diff --git a/.gitignore b/.gitignore index 18bc2be1e..f3265bf6d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ rebel.xml /web/nbactions.xml /forms/.rebel.xml.bak /derby.log -/LOG_DIR_IS_UNDEFINED/ \ No newline at end of file +/LOG_DIR_IS_UNDEFINED/ +/ehcache-diskstore/ \ No newline at end of file diff --git a/README.md b/README.md index 85186bef5..4fa35266e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Presentation -OCE is a project that allows importing the Vietnam public procurement data, available in the common MS Excel format, into a native [Open Contracting Data Standard (OCDS)](http://standard.open-contracting.org/) NoSQL storage, and then run visual data analytics (display a *live* dashboard with charts, maps and data tables as well as custom comparison charts). Since the data is natively stored in the OCDS format, it can be readily exported in this format without any transformation required, and with great throughput. +OCE is a project that allows importing the public procurement data, available in the common MS Excel format, into a native [Open Contracting Data Standard (OCDS)](http://standard.open-contracting.org/) NoSQL storage, and then run visual data analytics (display a *live* dashboard with charts, maps and data tables as well as custom comparison charts). Since the data is natively stored in the OCDS format, it can be readily exported in this format without any transformation required, and with great throughput. ## Visual Identity SVG and raster version of the logo and favicon can be found in the [`docs/images`](./docs/images/) directory. diff --git a/checkstyle.xml b/checkstyle.xml index 41e92e6e7..d90aa6ce9 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -47,7 +47,9 @@ - + + + diff --git a/forms/.gitignore b/forms/.gitignore index 5741bc597..1fc9006bf 100644 --- a/forms/.gitignore +++ b/forms/.gitignore @@ -5,3 +5,4 @@ .project /.springBeans /.checkstyle +/ehcache-diskstore/ \ No newline at end of file diff --git a/forms/pom.xml b/forms/pom.xml index a9ce600bd..ded400db2 100644 --- a/forms/pom.xml +++ b/forms/pom.xml @@ -29,10 +29,10 @@ UTF-8 1.8 - 7.7.0 - 0.10.14 - 1.11 - 0.5.5 + 7.9.0 + 0.10.16 + 1.12 + 0.5.6 v20160822 @@ -124,13 +124,13 @@ cglib 3.1 - + - com.google.javascript - closure-compiler - ${closure.compiler.version} - - + com.google.javascript + closure-compiler + ${closure.compiler.version} + + de.agilecoders.wicket.webjars @@ -218,14 +218,13 @@ - javax.mail - mail - 1.4.7 + org.springframework.boot + spring-boot-starter-mail org.apache.poi poi - ${pentaho.poi.version} + ${poi.version} @@ -355,11 +354,11 @@ org.hibernate - hibernate-entitymanager + hibernate-entitymanager org.hibernate - hibernate-envers + hibernate-envers diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html new file mode 100644 index 000000000..eb8bec1d8 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html @@ -0,0 +1,14 @@ + + + + +EditUserDashboardPage + + + +
+
+
+
+ + diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java new file mode 100644 index 000000000..422bdc644 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2016 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.forms.wicket.page.edit; + +import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.markup.html.form.validation.AbstractFormValidator; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.devgateway.ocds.forms.wicket.page.list.ListAllColorIndicatorPage; +import org.devgateway.ocds.persistence.dao.ColorIndicatorPair; +import org.devgateway.ocds.persistence.mongo.flags.FlagsConstants; +import org.devgateway.ocds.persistence.repository.ColorIndicatorPairRepository; +import org.devgateway.toolkit.forms.wicket.components.form.ColorPickerBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.Select2ChoiceBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; +import org.devgateway.toolkit.forms.wicket.providers.GenericChoiceProvider; +import org.devgateway.toolkit.persistence.repository.PersonRepository; +import org.devgateway.toolkit.web.security.SecurityConstants; +import org.wicketstuff.annotation.mount.MountPath; + +@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_ADMIN) +@MountPath("/editColorIndicatorPairPage") +public class EditColorIndicatorPairPage extends AbstractEditPage { + + private static final long serialVersionUID = -6069250112046118104L; + + @Override + protected ColorIndicatorPair newInstance() { + return new ColorIndicatorPair(); + } + + @SpringBean + private ColorIndicatorPairRepository colorIndicatorPairRepository; + + @SpringBean + private PersonRepository personRepository; + + private Select2ChoiceBootstrapFormComponent firstIndicator; + + private Select2ChoiceBootstrapFormComponent secondIndicator; + + public EditColorIndicatorPairPage(final PageParameters parameters) { + super(parameters); + this.jpaRepository = colorIndicatorPairRepository; + this.listPageClass = ListAllColorIndicatorPage.class; + + } + + + @Override + protected void onInitialize() { + super.onInitialize(); + + firstIndicator = + new Select2ChoiceBootstrapFormComponent("firstIndicator", + new GenericChoiceProvider(FlagsConstants.FLAGS_LIST)); + firstIndicator.required(); + editForm.add(firstIndicator); + + secondIndicator = + new Select2ChoiceBootstrapFormComponent("secondIndicator", + new GenericChoiceProvider(FlagsConstants.FLAGS_LIST)); + secondIndicator.required(); + editForm.add(secondIndicator); + + ColorPickerBootstrapFormComponent color = new ColorPickerBootstrapFormComponent("color"); + color.required(); + editForm.add(color); + editForm.add(new ColorIndicatorDistinctFormValidator()); + editForm.add(new ColorIndicatorUniquePairFormValidator(compoundModel)); + + } + + + private class ColorIndicatorDistinctFormValidator extends AbstractFormValidator { + + @Override + public FormComponent[] getDependentFormComponents() { + return new FormComponent[]{firstIndicator.getField(), secondIndicator.getField()}; + } + + @Override + public void validate(Form form) { + if (firstIndicator.getField().getValue() != null && secondIndicator.getField().getValue() != null + && firstIndicator.getField().getValue().equals(secondIndicator.getField().getValue())) { + error(firstIndicator.getField()); + error(secondIndicator.getField()); + } + } + } + + + private class ColorIndicatorUniquePairFormValidator extends AbstractFormValidator { + + private final IModel masterModel; + + ColorIndicatorUniquePairFormValidator(IModel masterModel) { + this.masterModel = masterModel; + } + + @Override + public FormComponent[] getDependentFormComponents() { + return new FormComponent[]{firstIndicator.getField(), secondIndicator.getField()}; + } + + @Override + public void validate(Form form) { + if (firstIndicator.getField().getValue() != null && secondIndicator.getField().getValue() != null) { + ColorIndicatorPair indicator = colorIndicatorPairRepository. + findByFirstIndicatorAndSecondIndicator(firstIndicator.getField().getValue(), + secondIndicator.getField().getValue()); + + if ((masterModel.getObject().isNew() && indicator != null) + || (!masterModel.getObject().isNew() && indicator != null + && !indicator.getId().equals(masterModel.getObject().getId()))) { + error(firstIndicator.getField()); + error(secondIndicator.getField()); + } + } + } + } +} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties new file mode 100644 index 000000000..35fb0cdfc --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties @@ -0,0 +1,17 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +page.title=Edit Color Indicator Pair +firstIndicator.label=First Indicator +secondIndicator.label=Second Indicator +color.label=Color +ColorIndicatorDistinctFormValidator=Please select two distinct indicators +ColorIndicatorUniquePairFormValidator=A similar pair of indicators was already saved with a color. You can add only distinct pairs. \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.java new file mode 100644 index 000000000..b8ae91a18 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2016 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.forms.wicket.page.list; + +import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.devgateway.ocds.forms.wicket.page.edit.EditColorIndicatorPairPage; +import org.devgateway.ocds.persistence.dao.ColorIndicatorPair; +import org.devgateway.ocds.persistence.repository.ColorIndicatorPairRepository; +import org.devgateway.toolkit.forms.wicket.page.lists.AbstractListPage; +import org.devgateway.toolkit.web.security.SecurityConstants; +import org.wicketstuff.annotation.mount.MountPath; + +@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_ADMIN) +@MountPath(value = "/listColorIndicators") +public class ListAllColorIndicatorPage extends AbstractListPage { + + @SpringBean + protected ColorIndicatorPairRepository colorIndicatorPairRepository; + + + + public ListAllColorIndicatorPage(final PageParameters pageParameters) { + super(pageParameters); + this.jpaRepository = colorIndicatorPairRepository; + this.editPageClass = EditColorIndicatorPairPage.class; + columns.add(new PropertyColumn(new Model("First Indicator"), + "firstIndicator", "firstIndicator")); + + columns.add(new PropertyColumn(new Model("Second Indicator"), + "secondIndicator", "secondIndicator")); + + columns.add(new PropertyColumn(new Model("Color"), + "color", "color")); + + } + +} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.properties b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.properties new file mode 100644 index 000000000..ed3f01ed0 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.properties @@ -0,0 +1,15 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +page.title=All Color Indicator Pairs +defaultDashboardUsers=Default Dashboard For Users +users=Users +view=View \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java b/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java index 5bcc5129f..2ba6345ce 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java @@ -153,6 +153,13 @@ public void clearReportsApiCache() { if (cache != null) { cache.removeAll(); } + + // get the reports cache "excelExportCache", declared in ehcache.xml + Cache excelExportCache = cm.getCache("excelExportCache"); + + if (excelExportCache != null) { + excelExportCache.removeAll(); + } } private String createCacheKey(final String outputType, final String reportName, final String parameters) { diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/FormsWebApplication.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/FormsWebApplication.java index 1a379673b..a952a8331 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/FormsWebApplication.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/FormsWebApplication.java @@ -64,7 +64,6 @@ * method. * * @author Stefan Kloe, mpostelnicu - * */ @EnableScheduling @SpringBootApplication @@ -84,16 +83,15 @@ public class FormsWebApplication extends AuthenticatedWebApplication { private SessionFinderService sessionFinderService; - public static void main(final String[] args) { SpringApplication.run(FormsWebApplication.class, args); } /** * @see org.apache.wicket.Application#newConverterLocator() This adds the - * {@link NonNumericFilteredBigDecimalConverter} as the standard - * {@link BigDecimal} converter for ALL fields using this type accross - * the application + * {@link NonNumericFilteredBigDecimalConverter} as the standard + * {@link BigDecimal} converter for ALL fields using this type accross + * the application **/ @Override protected IConverterLocator newConverterLocator() { @@ -111,8 +109,10 @@ private void configureSummernote() { SummernoteConfig.addStorage(new SummernoteFileStorage(STORAGE_ID, folder)); // mount the resource reference responsible for image uploads - mountResource(SummernoteStoredImageResourceReference.SUMMERNOTE_MOUNT_PATH, - new SummernoteStoredImageResourceReference(STORAGE_ID)); + mountResource( + SummernoteStoredImageResourceReference.SUMMERNOTE_MOUNT_PATH, + new SummernoteStoredImageResourceReference(STORAGE_ID) + ); } /** @@ -160,8 +160,10 @@ private void optimizeForWebPerformance() { // -Dwicket.configuration=deployment // The default is Development, so this code is not used if (usesDeploymentConfig()) { - getResourceSettings().setCachingStrategy(new FilenameWithVersionResourceCachingStrategy("-v-", - new CachingResourceVersion(new Adler32ResourceVersion()))); + getResourceSettings().setCachingStrategy(new FilenameWithVersionResourceCachingStrategy( + "-v-", + new CachingResourceVersion(new Adler32ResourceVersion()) + )); getResourceSettings().setJavaScriptCompressor( new GoogleClosureJavaScriptCompressor(CompilationLevel.SIMPLE_OPTIMIZATIONS)); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Layout.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Layout.java index 699a896c8..6e48b0bec 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Layout.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Layout.java @@ -22,6 +22,8 @@ public final class Layout implements Serializable { private final Boolean showlegend; + private final Legend legend; + private final Xaxis xaxis; private final Yaxis yaxis; @@ -36,6 +38,7 @@ public Layout(final LayoutBuilder layoutBuilder) { this.annotations = layoutBuilder.annotations; this.font = layoutBuilder.font; this.showlegend = layoutBuilder.showlegend; + this.legend = layoutBuilder.legend; this.xaxis = layoutBuilder.xaxis; this.yaxis = layoutBuilder.yaxis; this.bargap = layoutBuilder.bargap; @@ -56,6 +59,8 @@ public static class LayoutBuilder { private Boolean showlegend; + private Legend legend; + private Xaxis xaxis; private Yaxis yaxis; @@ -97,6 +102,11 @@ public LayoutBuilder setShowlegend(final Boolean showlegend) { return this; } + public LayoutBuilder setLegend(final Legend legend) { + this.legend = legend; + return this; + } + public LayoutBuilder setXaxis(final Xaxis xaxis) { this.xaxis = xaxis; return this; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Legend.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Legend.java new file mode 100644 index 000000000..af372dbb7 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Legend.java @@ -0,0 +1,28 @@ +package org.devgateway.toolkit.forms.wicket.components.charts; + +import java.io.Serializable; + +/** + * @author idobre + * @since 29/10/2017 + */ +public class Legend implements Serializable { + private final String orientation; + + public Legend(final LegendBuilder legendBuilder) { + this.orientation = legendBuilder.orientation; + } + + public static class LegendBuilder { + private String orientation; + + public LegendBuilder setOrientation(final String orientation) { + this.orientation = orientation; + return this; + } + + public Legend build() { + return new Legend(this); + } + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/PlotlyChart.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/PlotlyChart.java index f7a3dd567..5f40773b9 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/PlotlyChart.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/PlotlyChart.java @@ -13,10 +13,12 @@ /** * @author idobre * @since 4/19/17 + * + * Component that renders a chart using Plot.ly JS library. */ public final class PlotlyChart extends Panel { private static final String JS_FILE = "plotlyChart.js"; - private static final String INIT_FUNCTION = "init"; + private static final String INIT_FUNCTION = "initChart"; public static final String CHART_TYPE_PIE = "pie"; public static final String CHART_TYPE_BAR = "bar"; @@ -37,6 +39,9 @@ public final class PlotlyChart extends Panel { public static final String AXIS_TYPE_CATEGORY = "category"; + public static final String LEGEND_ORIENTATION_H = "h"; + public static final String LEGEND_ORIENTATION_V = "v"; + private final WebMarkupContainer chart; private final ChartParameters parameters; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/plotlyChart.js b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/plotlyChart.js index 37f23efd9..5b992e0db 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/plotlyChart.js +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/plotlyChart.js @@ -1,13 +1,27 @@ /** * init function that is called from the Wicket Component */ -var init = function (parameters) { +var initChart = function (parameters) { 'use strict'; + if (parameters === undefined) { + return; + } + + // check if we have the 'colors' property for a marker and copy it to 'color' property as well. + for(var i = 0; i < parameters.data.length; i++) { + if (parameters.data[i].marker !== undefined && parameters.data[i].marker.colors !== undefined) { + parameters.data[i].marker.color = parameters.data[i].marker.colors; + } + } + var chart = new PlotlyChart(parameters); chart.render(); }; +// run 'initChart' function to avoid being removed by the javascript optimizer. +initChart(); + /** * Object.assign for ES5 */ @@ -39,7 +53,7 @@ if (typeof Object.assign != 'function') { PlotlyChart.prototype.defaultProps = { chartId: 'chartId', layout: { - showlegend: true, + showlegend: true } }; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/AJAXDownload.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/AJAXDownload.java new file mode 100644 index 000000000..9f89f97af --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/AJAXDownload.java @@ -0,0 +1,45 @@ +package org.devgateway.toolkit.forms.wicket.components.form; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.behavior.AbstractAjaxBehavior; +import org.apache.wicket.request.IRequestHandler; + +/** + * AJAX update and file download. + * + * https://cwiki.apache.org/confluence/display/WICKET/AJAX+update+and+file+download+in+one+blow + */ +public abstract class AJAXDownload extends AbstractAjaxBehavior { + private boolean addAntiCache; + + public AJAXDownload() { + this(true); + } + + public AJAXDownload(final boolean addAntiCache) { + super(); + this.addAntiCache = addAntiCache; + } + + /** + * Call this method to initiate the download. + */ + public void initiate(final AjaxRequestTarget target) { + String url = getCallbackUrl().toString(); + + if (addAntiCache) { + url = url + (url.contains("?") ? "&" : "?"); + url = url + "antiCache=" + System.currentTimeMillis(); + } + + // the timeout is needed to let Wicket release the channel + target.appendJavaScript("setTimeout(\"window.location.href='" + url + "'\", 100);"); + } + + @Override + public void onRequest() { + getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(getHandler()); + } + + protected abstract IRequestHandler getHandler(); +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.html new file mode 100644 index 000000000..dcc4a01c3 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.java new file mode 100644 index 000000000..24b5b094e --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +/** + * + */ +package org.devgateway.toolkit.forms.wicket.components.form; + +import de.agilecoders.wicket.core.util.Attributes; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.ColorPickerTextField; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.model.IModel; + +/** + * @author mpostelnicu + * + */ +public class ColorPickerBootstrapFormComponent extends GenericBootstrapFormComponent { + + private Boolean isFloatedInput = false; + + public ColorPickerBootstrapFormComponent(final String id, final IModel labelModel, + final IModel model) { + super(id, labelModel, model); + } + + public ColorPickerBootstrapFormComponent(final String id, final IModel model) { + super(id, model); + } + + /** + * @param id + */ + public ColorPickerBootstrapFormComponent(final String id) { + super(id); + } + + @Override + protected ColorPickerTextField inputField(final String id, final IModel model) { + return new ColorPickerTextField(id, initFieldModel()); + } + + @Override + protected void onComponentTag(final ComponentTag tag) { + super.onComponentTag(tag); + + if (getIsFloatedInput()) { + Attributes.addClass(tag, "floated-input"); + } + } + + + public Boolean getIsFloatedInput() { + return isFloatedInput; + } + + public void setIsFloatedInput(final Boolean isFloatedInput) { + this.isFloatedInput = isFloatedInput; + } + +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java index 93817b35b..6526ac9b4 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java @@ -113,6 +113,9 @@ protected FormComponent updatingBehaviorComponent() { } protected void getAjaxFormComponentUpdatingBehavior() { + if (getUpdateEvent() == null) { + return; + } updatingBehaviorComponent().add(new AjaxFormComponentUpdatingBehavior(getUpdateEvent()) { private static final long serialVersionUID = -2696538086634114609L; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/DateFilteredBootstrapPropertyColumn.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/DateFilteredBootstrapPropertyColumn.java new file mode 100644 index 000000000..1ef18fcc7 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/DateFilteredBootstrapPropertyColumn.java @@ -0,0 +1,36 @@ +package org.devgateway.toolkit.forms.wicket.components.table; + +import java.util.Date; +import org.apache.wicket.AttributeModifier; +import org.apache.wicket.Component; +import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterForm; +import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.TextFilteredPropertyColumn; +import org.apache.wicket.model.IModel; +import org.devgateway.toolkit.forms.wicket.components.form.DateFieldBootstrapFormComponent; + +/** + * A TextFilteredPropertyColumn that uses DateFieldBootstrapFormComponent as a + * filter. + * + * Created by mpostelnicu + */ +public class DateFilteredBootstrapPropertyColumn extends TextFilteredPropertyColumn { + + public DateFilteredBootstrapPropertyColumn(final IModel displayModel, final S sortProperty, + final String propertyExpression) { + super(displayModel, sortProperty, propertyExpression); + } + + public DateFilteredBootstrapPropertyColumn(final IModel displayModel, final String propertyExpression) { + super(displayModel, propertyExpression); + } + + @Override + public Component getFilter(final String componentId, final FilterForm form) { + final DateFieldBootstrapFormComponent dateField = + new DateFieldBootstrapFormComponent(componentId, getFilterModel(form)); + dateField.hideLabel(); + dateField.getField().add(AttributeModifier.replace("onchange", "this.form.submit();")); + return dateField; + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/JpaFilterState.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/JpaFilterState.java index b7fc390ac..88d1311a6 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/JpaFilterState.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/JpaFilterState.java @@ -1,5 +1,6 @@ package org.devgateway.toolkit.forms.wicket.components.table; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.data.jpa.domain.Specification; import java.io.Serializable; @@ -11,6 +12,7 @@ public class JpaFilterState implements Serializable { private static final long serialVersionUID = 2241550275925712593L; + @JsonIgnore public Specification getSpecification() { return (root, query, cb) -> cb.and(); } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java index 3e0b29814..885c6330a 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java @@ -47,6 +47,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.resource.JQueryResourceReference; import org.apache.wicket.util.string.StringValue; +import org.devgateway.ocds.forms.wicket.page.list.ListAllColorIndicatorPage; import org.devgateway.ocds.forms.wicket.page.list.ListAllDashboardsPage; import org.devgateway.ocds.forms.wicket.page.list.ListMyDashboardsPage; import org.devgateway.toolkit.forms.WebConstants; @@ -284,6 +285,11 @@ protected List newSubMenuButtons(final String arg0) { list.add(new MenuBookmarkablePageLink(ListUserPage.class, null, new StringResourceModel("navbar.users", this, null)).setIconType(FontAwesomeIconType.users)); + list.add(new MenuBookmarkablePageLink(ListAllColorIndicatorPage.class, null, + new StringResourceModel("navbar.colorindicators", this, null)) + .setIconType(FontAwesomeIconType.paint_brush)); + + list.add(new MenuBookmarkablePageLink(SpringEndpointsPage.class, null, new StringResourceModel("navbar.springendpoints", this, null)) .setIconType(FontAwesomeIconType.anchor)); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties index affdef1c6..42185791c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties @@ -23,4 +23,5 @@ home=Home navbar.lang=Language navbar.jminix=JMX Console navbar.allDashboard=All Saved Dashboards -mydashboards=My Dashboards \ No newline at end of file +mydashboards=My Dashboards +navbar.colorindicators=Color Indicator Pairs \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html index 5b08a747d..445d69f49 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html @@ -31,6 +31,10 @@ diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java index ba7bb04ee..b7f8c3fa2 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java @@ -1,17 +1,21 @@ package org.devgateway.toolkit.forms.wicket.page; + +import org.springframework.cache.CacheManager; +import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxFallbackLink; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.validation.validator.RangeValidator; -import org.devgateway.toolkit.web.security.SecurityConstants; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxToggleBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; import org.devgateway.toolkit.persistence.dao.AdminSettings; import org.devgateway.toolkit.persistence.repository.AdminSettingsRepository; +import org.devgateway.toolkit.web.security.SecurityConstants; import org.wicketstuff.annotation.mount.MountPath; import java.util.List; @@ -32,6 +36,15 @@ public class EditAdminSettingsPage extends AbstractEditPage { private CheckBoxToggleBootstrapFormComponent disableApiSecurity; + private TextFieldBootstrapFormComponent adminEmail; + + private CheckBoxToggleBootstrapFormComponent enableDailyAutomatedImport; + + private TextFieldBootstrapFormComponent importFilesPath; + + @SpringBean + private CacheManager cacheManager; + @SpringBean protected AdminSettingsRepository adminSettingsRepository; @@ -72,8 +85,31 @@ protected void onInitialize() { // rebootServer = new CheckBoxToggleBootstrapFormComponent("rebootServer"); // editForm.add(rebootServer); - disableApiSecurity = new CheckBoxToggleBootstrapFormComponent("disableApiSecurity"); editForm.add(disableApiSecurity); + + enableDailyAutomatedImport = new CheckBoxToggleBootstrapFormComponent("enableDailyAutomatedImport"); + editForm.add(enableDailyAutomatedImport); + + + adminEmail = new TextFieldBootstrapFormComponent<>("adminEmail"); + editForm.add(adminEmail); + + importFilesPath = new TextFieldBootstrapFormComponent<>("importFilesPath"); + editForm.add(importFilesPath); + + addCacheClearLink(); + + } + + private void addCacheClearLink() { + IndicatingAjaxFallbackLink link = new IndicatingAjaxFallbackLink("clearCache") { + + @Override + public void onClick(AjaxRequestTarget target) { + cacheManager.getCacheNames().forEach(c -> cacheManager.getCache(c).clear()); + } + }; + editForm.add(link); } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties index 8c2858f17..d4b85289c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties @@ -6,4 +6,11 @@ rebootServer.label=Enable server reboot warning (just an example) disableApiSecurity.label=Disable API Security for /api/ endpoints (REQUIRES SERVER REBOOT) disableApiSecurity.help=This option will disable API security for any endpoint that starts with /api/. This should \ only be used for demo purposes, where full access to the data through the /api/ endpoints is not a problem. The \ - default option on new installations for this setting is OFF. \ No newline at end of file + default option on new installations for this setting is OFF. +adminEmail.label=Admin Notification Email +adminEmail.help=The email where notifications about scheduled activities can be sent +enableDailyAutomatedImport.label=Enable Daily Automated Import +enableDailyAutomatedImport.help=This will enable daily automated import and transformation of data from third party \ + sources +importFilesPath.label=Import Files Path +importFilesPath.help=The directory on the server where the automated import files are uploaded \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.html index 10e527d6d..6187932b4 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.html @@ -18,6 +18,7 @@
+
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java index a0b2bc7ff..7a4512123 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java @@ -1,16 +1,16 @@ -/******************************************************************************* +/** * Copyright (c) 2015 Development Gateway, Inc and others. - * + *

* All rights reserved. This program and the accompanying materials * are made available under the terms of the MIT License (MIT) * which accompanies this distribution, and is available at * https://opensource.org/licenses/MIT - * + *

* Contributors: * Development Gateway - initial API and implementation - *******************************************************************************/ + */ /** - * + * */ package org.devgateway.toolkit.forms.wicket.page; @@ -21,6 +21,7 @@ import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxPickerBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxToggleBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.ColorPickerBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.DateFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.DateTimeFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.FileInputBootstrapFormComponent; @@ -133,6 +134,10 @@ protected void onInitialize() { "preloadedEntitySelect", new GenericChoiceProvider<>(groupRepository.findAll())); preloadedEntitySelect.required(); editForm.add(preloadedEntitySelect); + + ColorPickerBootstrapFormComponent colorPicker = new ColorPickerBootstrapFormComponent("colorPicker"); + colorPicker.required(); + editForm.add(colorPicker); } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.properties index 5c7e00de5..252f53f1a 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.properties @@ -23,3 +23,4 @@ dateTime.label=Datetime fileInput.label=File Input summernote.label=Summernote Editor preloadedEntitySelect.label=Preloaded Entity Select +colorPicker.label=Color Picker diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/PlotlyChartDashboardExamplesPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/PlotlyChartDashboardExamplesPage.java index 748169a89..d6767bf19 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/PlotlyChartDashboardExamplesPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/PlotlyChartDashboardExamplesPage.java @@ -8,6 +8,7 @@ import org.devgateway.toolkit.forms.wicket.components.charts.Data; import org.devgateway.toolkit.forms.wicket.components.charts.Font; import org.devgateway.toolkit.forms.wicket.components.charts.Layout; +import org.devgateway.toolkit.forms.wicket.components.charts.Legend; import org.devgateway.toolkit.forms.wicket.components.charts.Line; import org.devgateway.toolkit.forms.wicket.components.charts.Marker; import org.devgateway.toolkit.forms.wicket.components.charts.PlotlyChart; @@ -112,6 +113,9 @@ private void addPieCharts() { .setWidth(WIDTH) .setHeight(HEIGHT) .setTitle("Global Emissions 1990-2011") + .setLegend(new Legend.LegendBuilder() + .setOrientation(PlotlyChart.LEGEND_ORIENTATION_H) + .build()) .setAnnotations( new ArrayList<>(Arrays.asList( new Annotation.AnnotationBuilder() diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.html index d8d134b58..07e2b2dd5 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.html @@ -2,6 +2,17 @@ +

+
+
+ +
+
+
+
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java index 88fcadce8..336eee5d1 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java @@ -11,10 +11,17 @@ *******************************************************************************/ package org.devgateway.toolkit.forms.wicket.page.lists; +import java.io.BufferedOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.ladda.LaddaAjaxButton; import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; @@ -22,16 +29,22 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterToolbar; import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.IFilteredColumn; import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.request.IRequestCycle; +import org.apache.wicket.request.IRequestHandler; +import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.util.lang.Classes; import org.devgateway.toolkit.forms.WebConstants; import org.devgateway.toolkit.forms.exceptions.NullEditPageClassException; import org.devgateway.toolkit.forms.exceptions.NullJpaRepositoryException; +import org.devgateway.toolkit.forms.wicket.components.form.AJAXDownload; import org.devgateway.toolkit.forms.wicket.components.table.AjaxFallbackBootstrapDataTable; import org.devgateway.toolkit.forms.wicket.components.table.JpaFilterState; import org.devgateway.toolkit.forms.wicket.components.table.ResettingFilterForm; @@ -40,12 +53,17 @@ import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; import org.devgateway.toolkit.forms.wicket.providers.SortableJpaRepositoryDataProvider; import org.devgateway.toolkit.persistence.dao.GenericPersistable; +import org.devgateway.toolkit.persistence.excel.service.ExcelGeneratorService; import org.devgateway.toolkit.persistence.repository.BaseJpaRepository; import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapBookmarkablePageLink; import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons; import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons.Size; import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeIconType; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +import javax.servlet.http.HttpServletResponse; /** * @author mpostelnicu This class can be use to display a list of Categories @@ -55,11 +73,18 @@ public abstract class AbstractListPage extends BasePage { private static final long serialVersionUID = 1958350868666244087L; + protected SortableJpaRepositoryDataProvider dataProvider; + protected BootstrapBookmarkablePageLink editPageLink; + protected Form excelForm; + + @SpringBean + protected ExcelGeneratorService excelGeneratorService; + /** * Get a stub print button that does nothing - * + * * @param pageParameters * @return */ @@ -126,7 +151,7 @@ public ActionPanel getActionPanel(final String id, final IModel model) { public SortableJpaRepositoryDataProvider getProvider() { return new SortableJpaRepositoryDataProvider<>(jpaRepository); } - + @Override protected void onInitialize() { super.onInitialize(); @@ -138,16 +163,22 @@ protected void onInitialize() { throw new NullEditPageClassException(); } - SortableJpaRepositoryDataProvider dataProvider = getProvider(); + dataProvider = new SortableJpaRepositoryDataProvider<>(jpaRepository); dataProvider.setFilterState(newFilterState()); - + + // create the excel download form; by default this form is hidden and we should make it visible only to pages + // where we want to export entities to excel file + excelForm = new ExcelDownloadForm("excelForm"); + excelForm.setVisibilityAllowed(false); + add(excelForm); + // add the 'Edit' button columns.add(new AbstractColumn(new StringResourceModel("actionsColumn", this, null)) { private static final long serialVersionUID = -7447601118569862123L; @Override public void populateItem(final Item> cellItem, final String componentId, - final IModel model) { + final IModel model) { cellItem.add(getActionPanel(componentId, model)); } }); @@ -188,4 +219,102 @@ public JpaFilterState newFilterState() { protected String getClassName() { return Classes.simpleName(getClass()); } + + /** + * A wrapper form that is used to fire the excel download action + */ + public class ExcelDownloadForm extends Form { + public ExcelDownloadForm(final String id) { + super(id); + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + final AJAXDownload download = new AJAXDownload() { + @Override + protected IRequestHandler getHandler() { + return new IRequestHandler() { + @Override + public void respond(final IRequestCycle requestCycle) { + final HttpServletResponse response = (HttpServletResponse) requestCycle + .getResponse().getContainerResponse(); + + try { + final int batchSize = 10000; + + final long count = excelGeneratorService.count( + jpaRepository, + dataProvider.getFilterState().getSpecification()); + + // if we need to export just one file then we don't create an archive + if (count <= batchSize) { + // set a maximum download of objects per excel file + final PageRequest pageRequest = new PageRequest(0, batchSize, + Sort.Direction.ASC, "id"); + + final byte[] bytes = excelGeneratorService.getExcelDownload( + jpaRepository, + dataProvider.getFilterState().getSpecification(), + pageRequest); + + response.setContentType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=excel-export.xlsx"); + response.getOutputStream().write(bytes); + } else { + response.setContentType("application/zip"); + response.setHeader("Content-Disposition", "attachment; filename=excel-export.zip"); + response.flushBuffer(); + final ZipOutputStream zout = new ZipOutputStream(new BufferedOutputStream( + response.getOutputStream())); + zout.setMethod(ZipOutputStream.DEFLATED); + zout.setLevel(Deflater.NO_COMPRESSION); + final int numberOfPages = (int) Math.ceil((double) count / batchSize); + for (int i = 0; i < numberOfPages; i++) { + final PageRequest pageRequest = new PageRequest(i, batchSize, + Sort.Direction.ASC, "id"); + final byte[] bytes = excelGeneratorService.getExcelDownload( + jpaRepository, + dataProvider.getFilterState().getSpecification(), + pageRequest); + final ZipEntry ze = new ZipEntry("excel-export-page " + (i + 1) + ".xlsx"); + zout.putNextEntry(ze); + zout.write(bytes, 0, bytes.length); + zout.closeEntry(); + response.flushBuffer(); + } + zout.close(); + response.flushBuffer(); + } + } catch (IOException e) { + logger.error(e); + } + + RequestCycle.get().scheduleRequestHandlerAfterCurrent(null); + } + + @Override + public void detach(final IRequestCycle requestCycle) { + // do nothing; + } + }; + } + }; + add(download); + + final LaddaAjaxButton excelButton = new LaddaAjaxButton("excelButton", + new Model<>("Excel Download"), + Buttons.Type.Warning) { + @Override + protected void onSubmit(final AjaxRequestTarget target, final Form form) { + // initiate the file download + download.initiate(target); + } + }; + excelButton.setIconType(FontAwesomeIconType.file_excel_o); + add(excelButton); + } + } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java index 53e8a155b..c6a474e7c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java @@ -40,6 +40,14 @@ public ListTestFormPage(final PageParameters pageParameters) { columns.add(new TextFilteredBootstrapPropertyColumn<>(new Model<>("Text Field"), "textField", "textField")); } + @Override + protected void onInitialize() { + super.onInitialize(); + + // enable excel download + excelForm.setVisibilityAllowed(true); + } + @Override public JpaFilterState newFilterState() { return new TestFormFilterState(); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListUserPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListUserPage.java index 804bd7c14..f0ff154e1 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListUserPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListUserPage.java @@ -40,4 +40,11 @@ public ListUserPage(final PageParameters pageParameters) { columns.add(new PropertyColumn(new Model("Roles"), "roles", "roles")); } + @Override + protected void onInitialize() { + super.onInitialize(); + + // enable excel download + excelForm.setVisibilityAllowed(true); + } } \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java index 8b57470ba..9297db43c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java @@ -35,7 +35,7 @@ import org.devgateway.toolkit.forms.WebConstants; import org.devgateway.toolkit.web.security.SecurityConstants; import org.devgateway.toolkit.web.security.SecurityUtil; -import org.devgateway.toolkit.forms.service.SendEmailService; +import org.devgateway.ocds.web.spring.SendEmailService; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.PasswordFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.Select2ChoiceBootstrapFormComponent; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java index 4e46b0239..40a74423b 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java @@ -10,7 +10,7 @@ import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; -import org.devgateway.toolkit.forms.service.SendEmailService; +import org.devgateway.ocds.web.spring.SendEmailService; import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.page.BasePage; import org.devgateway.toolkit.persistence.dao.Person; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java index 9a55686ec..9f0822f9e 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java @@ -62,6 +62,10 @@ class LoginForm extends BootstrapForm { private String referrer; + private TextFieldBootstrapFormComponent usernameField; + private PasswordFieldBootstrapFormComponent passwordField; + + LoginForm(final String id) { super(id); @@ -87,15 +91,28 @@ protected void onInitialize() { notificationPanel.setOutputMarkupId(true); add(notificationPanel); - final TextFieldBootstrapFormComponent username = new TextFieldBootstrapFormComponent<>("username", - new StringResourceModel("user", LoginPage.this, null), new PropertyModel(this, "username")); - username.required(); - add(username); - final PasswordFieldBootstrapFormComponent password = - new PasswordFieldBootstrapFormComponent("password", new PropertyModel<>(this, "password")); - password.getField().setResetPassword(false); - add(password); + usernameField = new TextFieldBootstrapFormComponent("username", + new StringResourceModel("user", LoginPage.this, null), + new PropertyModel(this, "username")) { + @Override + public String getUpdateEvent() { + return null; + } + }; + usernameField.required(); + add(usernameField); + + + passwordField = new PasswordFieldBootstrapFormComponent("password", + new PropertyModel<>(this, "password")) { + @Override + public String getUpdateEvent() { + return null; + } + }; + passwordField.getField().setResetPassword(false); + add(passwordField); final IndicatingAjaxButton submit = new IndicatingAjaxButton("submit", new StringResourceModel("submit.label", LoginPage.this, null)) { @@ -129,6 +146,9 @@ protected void onSubmit(final AjaxRequestTarget target, final Form form) { @Override protected void onError(final AjaxRequestTarget target, final Form form) { target.add(notificationPanel); + target.add(notificationPanel); + target.add(usernameField); + target.add(passwordField); } }; add(submit); diff --git a/persistence-mongodb/.gitignore b/persistence-mongodb/.gitignore index fca2ac261..1b1806514 100644 --- a/persistence-mongodb/.gitignore +++ b/persistence-mongodb/.gitignore @@ -6,3 +6,4 @@ /.settings/ /derby.log /.checkstyle +/ehcache-diskstore/ \ No newline at end of file diff --git a/persistence-mongodb/pom.xml b/persistence-mongodb/pom.xml index db0cca020..25dc8b455 100644 --- a/persistence-mongodb/pom.xml +++ b/persistence-mongodb/pom.xml @@ -176,9 +176,9 @@ - + - + @@ -190,6 +190,21 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + Jar Tests Package + package + + test-jar + + + + + org.apache.maven.plugins diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Address.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Address.java index 322d9bdd0..f48f877ff 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Address.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Address.java @@ -1,23 +1,22 @@ package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** - * An address. This may be the legally registered address of the organization, - * or may be a correspondence address for this particular contracting process. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#address - * + * Address + *

+ * An address. This may be the legally registered address of the organization, or may be a correspondence address for + * this particular contracting process. */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "streetAddress", "locality", @@ -28,55 +27,55 @@ public class Address { /** + * Street address + *

* The street address. For example, 1600 Amphitheatre Pkwy. - * */ - @ExcelExport @JsonProperty("streetAddress") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The street address. For example, 1600 Amphitheatre Pkwy.") private String streetAddress; - /** + * Locality + *

* The locality. For example, Mountain View. - * */ - @ExcelExport @JsonProperty("locality") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The locality. For example, Mountain View.") private String locality; - /** + * Region + *

* The region. For example, CA. - * */ - @ExcelExport @JsonProperty("region") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The region. For example, CA.") private String region; - /** + * Postal code + *

* The postal code. For example, 94043. - * */ - @ExcelExport @JsonProperty("postalCode") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The postal code. For example, 94043.") private String postalCode; - /** + * Country name + *

* The country name. For example, United States. - * */ - @ExcelExport @JsonProperty("countryName") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The country name. For example, United States.") private String countryName; /** + * Street address + *

* The street address. For example, 1600 Amphitheatre Pkwy. - * - * @return - * The streetAddress */ @JsonProperty("streetAddress") public String getStreetAddress() { @@ -84,21 +83,19 @@ public String getStreetAddress() { } /** + * Street address + *

* The street address. For example, 1600 Amphitheatre Pkwy. - * - * @param streetAddress - * The streetAddress */ @JsonProperty("streetAddress") - public void setStreetAddress(final String streetAddress) { + public void setStreetAddress(String streetAddress) { this.streetAddress = streetAddress; } /** + * Locality + *

* The locality. For example, Mountain View. - * - * @return - * The locality */ @JsonProperty("locality") public String getLocality() { @@ -106,21 +103,19 @@ public String getLocality() { } /** + * Locality + *

* The locality. For example, Mountain View. - * - * @param locality - * The locality */ @JsonProperty("locality") - public void setLocality(final String locality) { + public void setLocality(String locality) { this.locality = locality; } /** + * Region + *

* The region. For example, CA. - * - * @return - * The region */ @JsonProperty("region") public String getRegion() { @@ -128,21 +123,19 @@ public String getRegion() { } /** + * Region + *

* The region. For example, CA. - * - * @param region - * The region */ @JsonProperty("region") - public void setRegion(final String region) { + public void setRegion(String region) { this.region = region; } /** + * Postal code + *

* The postal code. For example, 94043. - * - * @return - * The postalCode */ @JsonProperty("postalCode") public String getPostalCode() { @@ -150,21 +143,19 @@ public String getPostalCode() { } /** + * Postal code + *

* The postal code. For example, 94043. - * - * @param postalCode - * The postalCode */ @JsonProperty("postalCode") - public void setPostalCode(final String postalCode) { + public void setPostalCode(String postalCode) { this.postalCode = postalCode; } /** + * Country name + *

* The country name. For example, United States. - * - * @return - * The countryName */ @JsonProperty("countryName") public String getCountryName() { @@ -172,34 +163,37 @@ public String getCountryName() { } /** + * Country name + *

* The country name. For example, United States. - * - * @param countryName - * The countryName */ @JsonProperty("countryName") - public void setCountryName(final String countryName) { + public void setCountryName(String countryName) { this.countryName = countryName; } @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("streetAddress", streetAddress) + .append("locality", locality) + .append("region", region) + .append("postalCode", postalCode) + .append("countryName", countryName) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(streetAddress). - append(locality). - append(region). - append(postalCode). - append(countryName). - toHashCode(); + return new HashCodeBuilder().append(streetAddress) + .append(postalCode) + .append(locality) + .append(countryName) + .append(region) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -207,14 +201,12 @@ public boolean equals(final Object other) { return false; } Address rhs = ((Address) other); - return new EqualsBuilder(). - append(streetAddress, rhs.streetAddress). - append(locality, rhs.locality). - append(region, rhs.region). - append(postalCode, rhs.postalCode). - append(countryName, rhs.countryName). - isEquals(); + return new EqualsBuilder().append(streetAddress, rhs.streetAddress) + .append(postalCode, rhs.postalCode) + .append(locality, rhs.locality) + .append(countryName, rhs.countryName) + .append(region, rhs.region) + .isEquals(); } } - diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amendment.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amendment.java index 1defaceae..337b99932 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amendment.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amendment.java @@ -1,68 +1,101 @@ package org.devgateway.ocds.persistence.mongo; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; /** - * Amendment information + * Amendment *

- * http://standard.open-contracting.org/latest/en/schema/reference/#amendment - * + * Amendment information */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "date", - "changes", - "rationale" + "rationale", + "id", + "description", + "amendsReleaseID", + "releaseID", + "changes" }) public class Amendment { /** - * Amendment Date + * Amendment date *

- * The data of this amendment. - * + * The date of this amendment. */ - @JsonProperty("date") - @Merge(MergeStrategy.overwrite) + @JsonPropertyDescription("The date of this amendment.") private Date date; - /** - * Amended fields + * Rationale *

- * Comma-seperated list of affected fields. - * - */ - @JsonProperty("changes") - @Merge(MergeStrategy.ocdsVersion) - private List changes = new ArrayList(); - - /** * An explanation for the amendment. - * */ @JsonProperty("rationale") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("An explanation for the amendment.") private String rationale; + /** + * ID + *

+ * An identifier for this amendment: often the amendment number + */ + @JsonProperty("id") + @JsonPropertyDescription("An identifier for this amendment: often the amendment number") + private String id; + /** + * Description + *

+ * A free text, or semi-structured, description of the changes made in this amendment. + */ + @JsonProperty("description") + @JsonPropertyDescription("A free text, or semi-structured, description of the changes made in this amendment.") + private String description; + /** + * Amended release (identifier) + *

+ * Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the + * values for this contracting process **before** the amendment was made. + */ + @JsonProperty("amendsReleaseID") + @JsonPropertyDescription("Provide the identifier (release.id) of the OCDS release (from this contracting process)" + + " that provides the values for this contracting process **before** the amendment was made.") + private String amendsReleaseID; + /** + * Amending release (identifier) + *

+ * Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the + * values for this contracting process **after** the amendment was made. + */ + @JsonProperty("releaseID") + @JsonPropertyDescription("Provide the identifier (release.id) of the OCDS release (from this contracting process)" + + " that provides the values for this contracting process **after** the amendment was made.") + private String releaseID; + /** + * Amended fields + *

+ * An array change objects describing the fields changed, and their former values. (Deprecated in 1.1) + */ + @JsonProperty("changes") + @JsonPropertyDescription("An array change objects describing the fields changed, and their former values. " + + "(Deprecated in 1.1)") + private List changes = new ArrayList(); /** - * Amendment Date + * Amendment date *

- * The data of this amendment. - * - * @return - * The date + * The date of this amendment. */ @JsonProperty("date") public Date getDate() { @@ -70,82 +103,166 @@ public Date getDate() { } /** - * Amendment Date + * Amendment date *

- * The data of this amendment. - * - * @param date - * The date + * The date of this amendment. */ @JsonProperty("date") - public void setDate(final Date date) { + public void setDate(Date date) { this.date = date; } /** - * Amended fields + * Rationale *

- * Comma-seperated list of affected fields. - * - * @return - * The changes + * An explanation for the amendment. */ - @JsonProperty("changes") - public List getChanges() { - return changes; + @JsonProperty("rationale") + public String getRationale() { + return rationale; } /** - * Amended fields + * Rationale *

- * Comma-seperated list of affected fields. - * - * @param changes - * The changes + * An explanation for the amendment. */ - @JsonProperty("changes") - public void setChanges(final List changes) { - this.changes = changes; + @JsonProperty("rationale") + public void setRationale(String rationale) { + this.rationale = rationale; } /** - * An explanation for the amendment. - * - * @return - * The rationale + * ID + *

+ * An identifier for this amendment: often the amendment number */ - @JsonProperty("rationale") - public String getRationale() { - return rationale; + @JsonProperty("id") + public String getId() { + return id; } /** - * An explanation for the amendment. - * - * @param rationale - * The rationale + * ID + *

+ * An identifier for this amendment: often the amendment number */ - @JsonProperty("rationale") - public void setRationale(final String rationale) { - this.rationale = rationale; + @JsonProperty("id") + public void setId(String id) { + this.id = id; + } + + /** + * Description + *

+ * A free text, or semi-structured, description of the changes made in this amendment. + */ + @JsonProperty("description") + public String getDescription() { + return description; + } + + /** + * Description + *

+ * A free text, or semi-structured, description of the changes made in this amendment. + */ + @JsonProperty("description") + public void setDescription(String description) { + this.description = description; + } + + /** + * Amended release (identifier) + *

+ * Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the + * values for this contracting process **before** the amendment was made. + */ + @JsonProperty("amendsReleaseID") + public String getAmendsReleaseID() { + return amendsReleaseID; } + /** + * Amended release (identifier) + *

+ * Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the + * values for this contracting process **before** the amendment was made. + */ + @JsonProperty("amendsReleaseID") + public void setAmendsReleaseID(String amendsReleaseID) { + this.amendsReleaseID = amendsReleaseID; + } + + /** + * Amending release (identifier) + *

+ * Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the + * values for this contracting process **after** the amendment was made. + */ + @JsonProperty("releaseID") + public String getReleaseID() { + return releaseID; + } + + /** + * Amending release (identifier) + *

+ * Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the + * values for this contracting process **after** the amendment was made. + */ + @JsonProperty("releaseID") + public void setReleaseID(String releaseID) { + this.releaseID = releaseID; + } + + /** + * Amended fields + *

+ * An array change objects describing the fields changed, and their former values. (Deprecated in 1.1) + */ + @JsonProperty("changes") + public List getChanges() { + return changes; + } + + /** + * Amended fields + *

+ * An array change objects describing the fields changed, and their former values. (Deprecated in 1.1) + */ + @JsonProperty("changes") + public void setChanges(List changes) { + this.changes = changes; + } + + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("date", date) + .append("rationale", rationale) + .append("id", id) + .append("description", description) + .append("amendsReleaseID", amendsReleaseID) + .append("releaseID", releaseID) + .append("changes", changes) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(date). - append(changes). - append(rationale). - toHashCode(); + return new HashCodeBuilder().append(date) + .append(releaseID) + .append(changes) + .append(description) + .append(id) + .append(amendsReleaseID) + .append(rationale) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -153,11 +270,14 @@ public boolean equals(final Object other) { return false; } Amendment rhs = ((Amendment) other); - return new EqualsBuilder(). - append(date, rhs.date). - append(changes, rhs.changes). - append(rationale, rhs.rationale). - isEquals(); + return new EqualsBuilder().append(date, rhs.date) + .append(releaseID, rhs.releaseID) + .append(changes, rhs.changes) + .append(description, rhs.description) + .append(id, rhs.id) + .append(amendsReleaseID, rhs.amendsReleaseID) + .append(rationale, rhs.rationale) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amount.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amount.java index f5fabf114..6e5d83f6e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amount.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amount.java @@ -1,53 +1,58 @@ -package org.devgateway.ocds.persistence.mongo; -import java.math.BigDecimal; +package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonValue; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + /** - * Value OCDS Entity http://standard.open-contracting.org/latest/en/schema/reference/#value - * - * We don't use Value class because of this bug: - * http://stackoverflow.com/questions/32343853/ - * spring-mongodb-driver-not-supporting-hierarchical-structure-of-java-domain-objec - * (hope this will be fixed in future versions of spring data mongodb) + * Value + *

*/ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "amount", "currency" }) public class Amount { + /** + * Amount + *

* Amount as a number. - * */ - @ExcelExport @JsonProperty("amount") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Amount as a number.") + @ExcelExport private BigDecimal amount; - /** - * The currency in 3-letter ISO 4217 format. - * + * Currency + *

+ * The currency for each amount should always be specified using the uppercase 3-letter currency code from ISO4217. */ - @ExcelExport @JsonProperty("currency") - @Merge(MergeStrategy.ocdsVersion) - private String currency; + @JsonPropertyDescription("The currency for each amount should always be specified using the uppercase 3-letter " + + "currency code from ISO4217.") + @ExcelExport + private Currency currency; + /** + * Amount + *

* Amount as a number. - * - * @return - * The amount */ @JsonProperty("amount") public BigDecimal getAmount() { @@ -55,53 +60,50 @@ public BigDecimal getAmount() { } /** + * Amount + *

* Amount as a number. - * - * @param amount - * The amount */ @JsonProperty("amount") - public void setAmount(final BigDecimal amount) { + public void setAmount(BigDecimal amount) { this.amount = amount; } /** - * The currency in 3-letter ISO 4217 format. - * - * @return - * The currency + * Currency + *

+ * The currency for each amount should always be specified using the uppercase 3-letter currency code from ISO4217. */ @JsonProperty("currency") - public String getCurrency() { + public Currency getCurrency() { return currency; } /** - * The currency in 3-letter ISO 4217 format. - * - * @param currency - * The currency + * Currency + *

+ * The currency for each amount should always be specified using the uppercase 3-letter currency code from ISO4217. */ @JsonProperty("currency") - public void setCurrency(final String currency) { + public void setCurrency(Currency currency) { this.currency = currency; } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("amount", amount) + .append("currency", currency) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(amount). - append(currency). - toHashCode(); + return new HashCodeBuilder().append(amount).append(currency).toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -109,9 +111,345 @@ public boolean equals(final Object other) { return false; } Amount rhs = ((Amount) other); - return new EqualsBuilder(). - append(amount, rhs.amount). - append(currency, rhs.currency). - isEquals(); + return new EqualsBuilder().append(amount, rhs.amount) + .append(currency, rhs.currency) + .isEquals(); + } + + public enum Currency { + + ADP("ADP"), + AED("AED"), + AFA("AFA"), + AFN("AFN"), + ALK("ALK"), + ALL("ALL"), + AMD("AMD"), + ANG("ANG"), + AOA("AOA"), + AOK("AOK"), + AON("AON"), + AOR("AOR"), + ARA("ARA"), + ARP("ARP"), + ARS("ARS"), + ARY("ARY"), + ATS("ATS"), + AUD("AUD"), + AWG("AWG"), + AYM("AYM"), + AZM("AZM"), + AZN("AZN"), + BAD("BAD"), + BAM("BAM"), + BBD("BBD"), + BDT("BDT"), + BEC("BEC"), + BEF("BEF"), + BEL("BEL"), + BGJ("BGJ"), + BGK("BGK"), + BGL("BGL"), + BGN("BGN"), + BHD("BHD"), + BIF("BIF"), + BMD("BMD"), + BND("BND"), + BOB("BOB"), + BOP("BOP"), + BOV("BOV"), + BRB("BRB"), + BRC("BRC"), + BRE("BRE"), + BRL("BRL"), + BRN("BRN"), + BRR("BRR"), + BSD("BSD"), + BTN("BTN"), + BUK("BUK"), + BWP("BWP"), + BYB("BYB"), + BYN("BYN"), + BYR("BYR"), + BZD("BZD"), + CAD("CAD"), + CDF("CDF"), + CHC("CHC"), + CHE("CHE"), + CHF("CHF"), + CHW("CHW"), + CLF("CLF"), + CLP("CLP"), + CNY("CNY"), + COP("COP"), + COU("COU"), + CRC("CRC"), + CSD("CSD"), + CSJ("CSJ"), + CSK("CSK"), + CUC("CUC"), + CUP("CUP"), + CVE("CVE"), + CYP("CYP"), + CZK("CZK"), + DDM("DDM"), + DEM("DEM"), + DJF("DJF"), + DKK("DKK"), + DOP("DOP"), + DZD("DZD"), + ECS("ECS"), + ECV("ECV"), + EEK("EEK"), + EGP("EGP"), + ERN("ERN"), + ESA("ESA"), + ESB("ESB"), + ESP("ESP"), + ETB("ETB"), + EUR("EUR"), + FIM("FIM"), + FJD("FJD"), + FKP("FKP"), + FRF("FRF"), + GBP("GBP"), + GEK("GEK"), + GEL("GEL"), + GHC("GHC"), + GHP("GHP"), + GHS("GHS"), + GIP("GIP"), + GMD("GMD"), + GNE("GNE"), + GNF("GNF"), + GNS("GNS"), + GQE("GQE"), + GRD("GRD"), + GTQ("GTQ"), + GWE("GWE"), + GWP("GWP"), + GYD("GYD"), + HKD("HKD"), + HNL("HNL"), + HRD("HRD"), + HRK("HRK"), + HTG("HTG"), + HUF("HUF"), + IDR("IDR"), + IEP("IEP"), + ILP("ILP"), + ILR("ILR"), + ILS("ILS"), + INR("INR"), + IQD("IQD"), + IRR("IRR"), + ISJ("ISJ"), + ISK("ISK"), + ITL("ITL"), + JMD("JMD"), + JOD("JOD"), + JPY("JPY"), + KES("KES"), + KGS("KGS"), + KHR("KHR"), + KMF("KMF"), + KPW("KPW"), + KRW("KRW"), + KWD("KWD"), + KYD("KYD"), + KZT("KZT"), + LAJ("LAJ"), + LAK("LAK"), + LBP("LBP"), + LKR("LKR"), + LRD("LRD"), + LSL("LSL"), + LSM("LSM"), + LTL("LTL"), + LTT("LTT"), + LUC("LUC"), + LUF("LUF"), + LUL("LUL"), + LVL("LVL"), + LVR("LVR"), + LYD("LYD"), + MAD("MAD"), + MDL("MDL"), + MGA("MGA"), + MGF("MGF"), + MKD("MKD"), + MLF("MLF"), + MMK("MMK"), + MNT("MNT"), + MOP("MOP"), + MRO("MRO"), + MTL("MTL"), + MTP("MTP"), + MUR("MUR"), + MVQ("MVQ"), + MVR("MVR"), + MWK("MWK"), + MXN("MXN"), + MXP("MXP"), + MXV("MXV"), + MYR("MYR"), + MZE("MZE"), + MZM("MZM"), + MZN("MZN"), + NAD("NAD"), + NGN("NGN"), + NIC("NIC"), + NIO("NIO"), + NLG("NLG"), + NOK("NOK"), + NPR("NPR"), + NZD("NZD"), + OMR("OMR"), + PAB("PAB"), + PEH("PEH"), + PEI("PEI"), + PEN("PEN"), + PES("PES"), + PGK("PGK"), + PHP("PHP"), + PKR("PKR"), + PLN("PLN"), + PLZ("PLZ"), + PTE("PTE"), + PYG("PYG"), + QAR("QAR"), + RHD("RHD"), + ROK("ROK"), + ROL("ROL"), + RON("RON"), + RSD("RSD"), + RUB("RUB"), + RUR("RUR"), + RWF("RWF"), + SAR("SAR"), + SBD("SBD"), + SCR("SCR"), + SDD("SDD"), + SDG("SDG"), + SDP("SDP"), + SEK("SEK"), + SGD("SGD"), + SHP("SHP"), + SIT("SIT"), + SKK("SKK"), + SLL("SLL"), + SOS("SOS"), + SRD("SRD"), + SRG("SRG"), + SSP("SSP"), + STD("STD"), + SUR("SUR"), + SVC("SVC"), + SYP("SYP"), + SZL("SZL"), + THB("THB"), + TJR("TJR"), + TJS("TJS"), + TMM("TMM"), + TMT("TMT"), + TND("TND"), + TOP("TOP"), + TPE("TPE"), + TRL("TRL"), + TRY("TRY"), + TTD("TTD"), + TWD("TWD"), + TZS("TZS"), + UAH("UAH"), + UAK("UAK"), + UGS("UGS"), + UGW("UGW"), + UGX("UGX"), + USD("USD"), + USN("USN"), + USS("USS"), + UYI("UYI"), + UYN("UYN"), + UYP("UYP"), + UYU("UYU"), + UZS("UZS"), + VEB("VEB"), + VEF("VEF"), + VNC("VNC"), + VND("VND"), + VUV("VUV"), + WST("WST"), + XAF("XAF"), + XAG("XAG"), + XAU("XAU"), + XBA("XBA"), + XBB("XBB"), + XBC("XBC"), + XBD("XBD"), + XCD("XCD"), + XDR("XDR"), + XEU("XEU"), + XFO("XFO"), + XFU("XFU"), + XOF("XOF"), + XPD("XPD"), + XPF("XPF"), + XPT("XPT"), + XRE("XRE"), + XSU("XSU"), + XTS("XTS"), + XUA("XUA"), + XXX("XXX"), + YDD("YDD"), + YER("YER"), + YUD("YUD"), + YUM("YUM"), + YUN("YUN"), + ZAL("ZAL"), + ZAR("ZAR"), + ZMK("ZMK"), + ZMW("ZMW"), + ZRN("ZRN"), + ZRZ("ZRZ"), + ZWC("ZWC"), + ZWD("ZWD"), + ZWL("ZWL"), + ZWN("ZWN"), + ZWR("ZWR"), + BTC("BTC"); //this is just for testing purposes, Bitcoin :) + private final String value; + private static final Map CONSTANTS = new HashMap(); + + static { + for (Currency c : values()) { + CONSTANTS.put(c.value, c); + } + } + + Currency(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static Currency fromValue(String value) { + Currency constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + } + } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Award.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Award.java index fbe413ee1..845e64e8f 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Award.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Award.java @@ -1,36 +1,34 @@ package org.devgateway.ocds.persistence.mongo; -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExportSepareteSheet; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Award *

- * An award for the given procurement. There may be more than one award per contracting process - * e.g. because the contract is split amongst different providers, or because it is a standing offer. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#award - * + * An award for the given procurement. There may be more than one award per contracting process e.g. because the + * contract is split among different providers, or because it is a standing offer. */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "title", @@ -42,134 +40,141 @@ "items", "contractPeriod", "documents", + "amendments", "amendment" }) -public class Award implements Identifiable { +public class Award { + /** * Award ID *

- * The identifier for this award. It must be unique and cannot change within the Open Contracting Process - * it is part of (defined by a single ocid). - * - * See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) - * for further details. + * The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is + * part of (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting + * .org/latest/en/schema/identifiers/) for further details. * (Required) - * */ - @ExcelExport @JsonProperty("id") - @Merge(MergeStrategy.overwrite) + @JsonPropertyDescription("The identifier for this award. It must be unique and cannot change within the Open " + + "Contracting Process it is part of (defined by a single ocid). See the [identifier guidance]" + + "(http://standard.open-contracting.org/latest/en/schema/identifiers/) for further details.") + @ExcelExport private String id; - /** + * Title + *

* Award title - * */ - @ExcelExport @JsonProperty("title") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Award title") + @ExcelExport private String title; - /** + * Description + *

* Award description - * */ - @ExcelExport @JsonProperty("description") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Award description") + @ExcelExport private String description; - /** - * Award Status + * Award status *

- * The current status of the award drawn from the - * [awardStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#award-status) - * + * The current status of the award drawn from the [awardStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#award-status) */ - @ExcelExport @JsonProperty("status") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The current status of the award drawn from the [awardStatus codelist](http://standard" + + ".open-contracting.org/latest/en/schema/codelists/#award-status)") + @ExcelExport private Status status; - /** * Award date *

* The date of the contract award. This is usually the date on which a decision to award was made. - * */ - @ExcelExport @JsonProperty("date") - @Merge(MergeStrategy.ocdsVersion) - private Date date; - + @JsonPropertyDescription("The date of the contract award. This is usually the date on which a decision to award " + + "was made.") @ExcelExport + private Date date; + /** + * Value + *

+ */ @JsonProperty("value") + @ExcelExport private Amount value; - /** - * The suppliers awarded this award. If different suppliers have been awarded different items of values, - * these should be split into separate award blocks. - * + * Suppliers + *

+ * The suppliers awarded this award. If different suppliers have been awarded different items of values, these + * should be split into separate award blocks. */ - @ExcelExport @JsonProperty("suppliers") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.ocdsVersion) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("The suppliers awarded this award. If different suppliers have been awarded different " + + "items of values, these should be split into separate award blocks.") + @ExcelExport private Set suppliers = new LinkedHashSet(); - /** - * Items Awarded + * Items awarded *

- * The goods and services awarded in this award, broken into line items wherever possible. - * Items should not be duplicated, but the quantity specified instead. - * + * The goods and services awarded in this award, broken into line items wherever possible. Items should not be + * duplicated, but the quantity specified instead. */ + @JsonProperty("items") + @JsonDeserialize(as = LinkedHashSet.class) @ExcelExport @ExcelExportSepareteSheet - @JsonProperty("items") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonPropertyDescription("The goods and services awarded in this award, broken into line items wherever possible." + + " Items should not be duplicated, but the quantity specified instead.") private Set items = new LinkedHashSet(); - /** * Period *

- * */ - @ExcelExport @JsonProperty("contractPeriod") + @JsonPropertyDescription(" ") + @ExcelExport private Period contractPeriod; - /** + * Documents + *

* All documents and attachments related to the award, including any notices. - * */ @JsonProperty("documents") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("All documents and attachments related to the award, including any notices.") private Set documents = new LinkedHashSet(); - /** - * Amendment information + * Amendments + *

+ * An award amendment is a formal change to the details of the award, and generally involves the publication of a + * new award notice/release. The rationale and a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + @JsonPropertyDescription("An award amendment is a formal change to the details of the award, and generally " + + "involves the publication of a new award notice/release. The rationale and a description of the changes" + + " made can be provided here.") + private List amendments = new ArrayList(); + /** + * Amendment *

- * - * + * Amendment information */ @JsonProperty("amendment") + @JsonPropertyDescription("Amendment information") + @ExcelExport private Amendment amendment; /** * Award ID *

- * The identifier for this award. It must be unique and cannot change within the Open Contracting Process - * it is part of (defined by a single ocid). - * - * See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) - * for further details. + * The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is + * part of (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting + * .org/latest/en/schema/identifiers/) for further details. * (Required) - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -179,26 +184,20 @@ public String getId() { /** * Award ID *

- * The identifier for this award. It must be unique and cannot change within the Open Contracting Process - * it is part of (defined by a single ocid). - * - * See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) - * for further details. + * The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is + * part of (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting + * .org/latest/en/schema/identifiers/) for further details. * (Required) - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** + * Title + *

* Award title - * - * @return - * The title */ @JsonProperty("title") public String getTitle() { @@ -206,21 +205,19 @@ public String getTitle() { } /** + * Title + *

* Award title - * - * @param title - * The title */ @JsonProperty("title") - public void setTitle(final String title) { + public void setTitle(String title) { this.title = title; } /** + * Description + *

* Award description - * - * @return - * The description */ @JsonProperty("description") public String getDescription() { @@ -228,41 +225,34 @@ public String getDescription() { } /** - * Award Status + * Description *

- * The current status of the award drawn from the - * [awardStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#award-status) - * - * @return - * The status + * Award description */ - @JsonProperty("status") - public Status getStatus() { - return status; + @JsonProperty("description") + public void setDescription(String description) { + this.description = description; } /** - * Award description - * - * @param description - * The description + * Award status + *

+ * The current status of the award drawn from the [awardStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#award-status) */ - @JsonProperty("description") - public void setDescription(final String description) { - this.description = description; + @JsonProperty("status") + public Status getStatus() { + return status; } /** - * Award Status + * Award status *

- * The current status of the award drawn from the - * [awardStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#award-status) - * - * @param status - * The status + * The current status of the award drawn from the [awardStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#award-status) */ @JsonProperty("status") - public void setStatus(final Status status) { + public void setStatus(Status status) { this.status = status; } @@ -270,9 +260,6 @@ public void setStatus(final Status status) { * Award date *

* The date of the contract award. This is usually the date on which a decision to award was made. - * - * @return - * The date */ @JsonProperty("date") public Date getDate() { @@ -283,19 +270,15 @@ public Date getDate() { * Award date *

* The date of the contract award. This is usually the date on which a decision to award was made. - * - * @param date - * The date */ @JsonProperty("date") - public void setDate(final Date date) { + public void setDate(Date date) { this.date = date; } /** - * - * @return - * The value + * Value + *

*/ @JsonProperty("value") public Amount getValue() { @@ -303,21 +286,19 @@ public Amount getValue() { } /** - * - * @param value - * The value + * Value + *

*/ @JsonProperty("value") - public void setValue(final Amount value) { + public void setValue(Amount value) { this.value = value; } /** - * The suppliers awarded this award. If different suppliers have been awarded different items of values, - * these should be split into separate award blocks. - * - * @return - * The suppliers + * Suppliers + *

+ * The suppliers awarded this award. If different suppliers have been awarded different items of values, these + * should be split into separate award blocks. */ @JsonProperty("suppliers") public Set getSuppliers() { @@ -325,25 +306,21 @@ public Set getSuppliers() { } /** - * The suppliers awarded this award. If different suppliers have been awarded different items of values, - * these should be split into separate award blocks. - * - * @param suppliers - * The suppliers + * Suppliers + *

+ * The suppliers awarded this award. If different suppliers have been awarded different items of values, these + * should be split into separate award blocks. */ @JsonProperty("suppliers") - public void setSuppliers(final Set suppliers) { + public void setSuppliers(Set suppliers) { this.suppliers = suppliers; } /** - * Items Awarded + * Items awarded *

- * The goods and services awarded in this award, broken into line items wherever possible. - * Items should not be duplicated, but the quantity specified instead. - * - * @return - * The items + * The goods and services awarded in this award, broken into line items wherever possible. Items should not be + * duplicated, but the quantity specified instead. */ @JsonProperty("items") public Set getItems() { @@ -351,26 +328,19 @@ public Set getItems() { } /** - * Items Awarded + * Items awarded *

- * The goods and services awarded in this award, broken into line items wherever possible. - * Items should not be duplicated, but the quantity specified instead. - * - * @param items - * The items + * The goods and services awarded in this award, broken into line items wherever possible. Items should not be + * duplicated, but the quantity specified instead. */ @JsonProperty("items") - public void setItems(final Set items) { + public void setItems(Set items) { this.items = items; } /** * Period *

- * - * - * @return - * The contractPeriod */ @JsonProperty("contractPeriod") public Period getContractPeriod() { @@ -380,21 +350,16 @@ public Period getContractPeriod() { /** * Period *

- * - * - * @param contractPeriod - * The contractPeriod */ @JsonProperty("contractPeriod") - public void setContractPeriod(final Period contractPeriod) { + public void setContractPeriod(Period contractPeriod) { this.contractPeriod = contractPeriod; } /** + * Documents + *

* All documents and attachments related to the award, including any notices. - * - * @return - * The documents */ @JsonProperty("documents") public Set getDocuments() { @@ -402,23 +367,41 @@ public Set getDocuments() { } /** + * Documents + *

* All documents and attachments related to the award, including any notices. - * - * @param documents - * The documents */ @JsonProperty("documents") - public void setDocuments(final Set documents) { + public void setDocuments(Set documents) { this.documents = documents; } /** - * Amendment information + * Amendments *

- * - * - * @return - * The amendment + * An award amendment is a formal change to the details of the award, and generally involves the publication of a + * new award notice/release. The rationale and a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + public List getAmendments() { + return amendments; + } + + /** + * Amendments + *

+ * An award amendment is a formal change to the details of the award, and generally involves the publication of a + * new award notice/release. The rationale and a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + public void setAmendments(List amendments) { + this.amendments = amendments; + } + + /** + * Amendment + *

+ * Amendment information */ @JsonProperty("amendment") public Amendment getAmendment() { @@ -426,42 +409,52 @@ public Amendment getAmendment() { } /** - * Amendment information + * Amendment *

- * - * - * @param amendment - * The amendment + * Amendment information */ @JsonProperty("amendment") - public void setAmendment(final Amendment amendment) { + public void setAmendment(Amendment amendment) { this.amendment = amendment; } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("id", id) + .append("title", title) + .append("description", description) + .append("status", status) + .append("date", date) + .append("value", value) + .append("suppliers", suppliers) + .append("items", items) + .append("contractPeriod", contractPeriod) + .append("documents", documents) + .append("amendments", amendments) + .append("amendment", amendment) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(id). - append(title). - append(description). - append(status). - append(date). - append(value). - append(suppliers). - append(items). - append(contractPeriod). - append(documents). - append(amendment). - toHashCode(); + return new HashCodeBuilder().append(date) + .append(amendment) + .append(suppliers) + .append(documents) + .append(description) + .append(amendments) + .append(title) + .append(contractPeriod) + .append(id) + .append(value) + .append(items) + .append(status) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -469,52 +462,52 @@ public boolean equals(final Object other) { return false; } Award rhs = ((Award) other); - return new EqualsBuilder(). - append(id, rhs.id). - append(title, rhs.title). - append(description, rhs.description). - append(status, rhs.status). - append(date, rhs.date). - append(value, rhs.value). - append(suppliers, rhs.suppliers). - append(items, rhs.items). - append(contractPeriod, rhs.contractPeriod). - append(documents, rhs.documents). - append(amendment, rhs.amendment). - isEquals(); + return new EqualsBuilder().append(date, rhs.date) + .append(amendment, rhs.amendment) + .append(suppliers, rhs.suppliers) + .append(documents, rhs.documents) + .append(description, rhs.description) + .append(amendments, rhs.amendments) + .append(title, rhs.title) + .append(contractPeriod, rhs.contractPeriod) + .append(id, rhs.id) + .append(value, rhs.value) + .append(items, rhs.items) + .append(status, rhs.status) + .isEquals(); } public enum Status { - pending("pending"), + pending("pending"), active("active"), - cancelled("cancelled"), - unsuccessful("unsuccessful"); - private final String value; - private static final Map CONSTANTS = new HashMap(); static { - for (Status c: values()) { + for (Status c : values()) { CONSTANTS.put(c.value, c); } } - Status(final String value) { + Status(String value) { this.value = value; } - @JsonValue @Override public String toString() { return this.value; } + @JsonValue + public String value() { + return this.value; + } + @JsonCreator - public static Status fromValue(final String value) { + public static Status fromValue(String value) { Status constant = CONSTANTS.get(value); if (constant == null) { throw new IllegalArgumentException(value); @@ -525,8 +518,4 @@ public static Status fromValue(final String value) { } - @Override - public Serializable getIdProperty() { - return id; - } -} \ No newline at end of file +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Bids.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Bids.java index fd415ab03..7f86b76d5 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Bids.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Bids.java @@ -1,8 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; @@ -14,9 +11,7 @@ import org.devgateway.ocds.persistence.mongo.merge.Merge; import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import java.util.HashMap; import java.util.LinkedHashSet; -import java.util.Map; import java.util.Set; @@ -59,8 +54,6 @@ public class Bids { @JsonDeserialize(as = java.util.LinkedHashSet.class) @Merge(MergeStrategy.ocdsVersion) private Set details = new LinkedHashSet<>(); - @JsonIgnore - private Map additionalProperties = new HashMap(); /** * Statistics @@ -113,23 +106,10 @@ public String toString() { return ToStringBuilder.reflectionToString(this); } - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @JsonAnySetter - public void setAdditionalProperty(String name, Object value) { - this.additionalProperties.put(name, value); - } - - public void setAdditionalProperties(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } @Override public int hashCode() { - return new HashCodeBuilder().append(statistics).append(details).append(additionalProperties).toHashCode(); + return new HashCodeBuilder().append(statistics).append(details).toHashCode(); } @Override @@ -142,7 +122,7 @@ public boolean equals(Object other) { } Bids rhs = ((Bids) other); return new EqualsBuilder().append(statistics, rhs.statistics).append(details, rhs.details) - .append(additionalProperties, rhs.additionalProperties).isEquals(); + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Budget.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Budget.java index 15a65e428..bb7aaa267 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Budget.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Budget.java @@ -1,147 +1,126 @@ package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.net.URI; + /** - * Budget Information + * Budget information *

- * This section contain information about the budget line, and associated projects, - * through which this contracting process is funded. - * It draws upon data model of the - * [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md), - * and should be used to cross-reference to more detailed information held using a Budget Data Package, or, - * where no linked Budget Data Package is available, to provide enough information to allow a user to manually or + * This section contain information about the budget line, and associated projects, through which this contracting + * process is funded. It draws upon data model of the [Fiscal Data Package](http://fiscal.dataprotocols.org/), and + * should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no + * linked Budget Data Package is available, to provide enough information to allow a user to manually or * automatically cross-reference with another published source of budget and project information. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#budget - * */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ - "source", "id", "description", "amount", "project", "projectID", - "uri" + "uri", + "source" }) public class Budget { /** - * Data Source + * ID *

- * Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where - * users can find further information on the budget line item identifiers, or project identifiers, provided here. - * - */ - @JsonProperty("source") - @Merge(MergeStrategy.ocdsVersion) - private String source; - - /** - * An identifier for the budget line item which provides funds for this contracting process. - * This identifier should be possible to cross-reference against the provided data source. - * + * An identifier for the budget line item which provides funds for this contracting process. This identifier + * should be possible to cross-reference against the provided data source. */ @JsonProperty("id") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("An identifier for the budget line item which provides funds for this contracting " + + "process. This identifier should be possible to cross-reference against the provided data source.") private String id; - /** * Budget Source *

- * A short free text description of the budget source. May be used to provide the title of the budget line, - * or the programme used to fund this project. - * + * A short free text description of the budget source. May be used to provide the title of the budget line, or + * the programme used to fund this project. */ - @ExcelExport @JsonProperty("description") - @Merge(MergeStrategy.ocdsVersion) - private String description; - + @JsonPropertyDescription("A short free text description of the budget source. May be used to provide the title of" + + " the budget line, or the programme used to fund this project.") @ExcelExport + private String description; + /** + * Value + *

+ */ @JsonProperty("amount") + @ExcelExport private Amount amount; - /** - * Project Title + * Project title *

- * The name of the project that through which this contracting process is funded (if applicable). - * Some organizations maintain a registry of projects, and the data should use the name by which the project - * is known in that registry. No translation option is offered for this string, as translated values - * can be provided in third-party data, linked from the data source above. - * + * The name of the project that through which this contracting process is funded (if applicable). Some + * organizations maintain a registry of projects, and the data should use the name by which the project is known + * in that registry. No translation option is offered for this string, as translated values can be provided in + * third-party data, linked from the data source above. */ - @ExcelExport @JsonProperty("project") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The name of the project that through which this contracting process is funded (if " + + "applicable). Some organizations maintain a registry of projects, and the data should use the name by " + + "which the project is known in that registry. No translation option is offered for this string, as " + + "translated values can be provided in third-party data, linked from the data source above.") + @ExcelExport private String project; - /** - * Project Identifier + * Project identifier *

- * An external identifier for the project that this contracting process forms part of, - * or is funded via (if applicable). Some organizations maintain a registry of projects, - * and the data should use the identifier from the relevant registry of projects. - * + * An external identifier for the project that this contracting process forms part of, or is funded via (if + * applicable). Some organizations maintain a registry of projects, and the data should use the identifier from + * the relevant registry of projects. */ @JsonProperty("projectID") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("An external identifier for the project that this contracting process forms part of, or " + + "is funded via (if applicable). Some organizations maintain a registry of projects, and the data should" + + " use the identifier from the relevant registry of projects.") private String projectID; - /** * Linked budget information *

- * A URI pointing directly to a machine-readable record about - * the related budget or projects for this contracting process. - * + * A URI pointing directly to a machine-readable record about the budget line-item or line-items that fund this + * contracting process. Information may be provided in a range of formats, including using IATI, the Open Fiscal + * Data Standard or any other standard which provides structured data on budget sources. Human readable documents + * can be included using the planning.documents block. */ @JsonProperty("uri") - @Merge(MergeStrategy.ocdsVersion) - private String uri; - + @JsonPropertyDescription("A URI pointing directly to a machine-readable record about the budget line-item or " + + "line-items that fund this contracting process. Information may be provided in a range of formats, " + + "including using IATI, the Open Fiscal Data Standard or any other standard which provides structured " + + "data on budget sources. Human readable documents can be included using the planning.documents block.") + private URI uri; /** * Data Source *

- * Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where - * users can find further information on the budget line item identifiers, or project identifiers, provided here. - * - * @return - * The source + * (Deprecated in 1.1) Used to point either to a corresponding Budget Data Package, or to a machine or + * human-readable source where users can find further information on the budget line item identifiers, or project + * identifiers, provided here. */ @JsonProperty("source") - public String getSource() { - return source; - } + @JsonPropertyDescription("(Deprecated in 1.1) Used to point either to a corresponding Budget Data Package, or to " + + "a machine or human-readable source where users can find further information on the budget line item " + + "identifiers, or project identifiers, provided here.") + private URI source; - /** - * Data Source - *

- * Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where - * users can find further information on the budget line item identifiers, or project identifiers, provided here. - * - * @param source - * The source - */ - @JsonProperty("source") - public void setSource(final String source) { - this.source = source; - } /** - * An identifier for the budget line item which provides funds for this contracting process. - * This identifier should be possible to cross-reference against the provided data source. - * - * @return - * The id + * ID + *

+ * An identifier for the budget line item which provides funds for this contracting process. This identifier + * should be possible to cross-reference against the provided data source. */ @JsonProperty("id") public String getId() { @@ -149,25 +128,21 @@ public String getId() { } /** - * An identifier for the budget line item which provides funds for this contracting process. - * This identifier should be possible to cross-reference against the provided data source. - * - * @param id - * The id + * ID + *

+ * An identifier for the budget line item which provides funds for this contracting process. This identifier + * should be possible to cross-reference against the provided data source. */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** * Budget Source *

- * A short free text description of the budget source. May be used to provide the title of the budget line, - * or the programme used to fund this project. - * - * @return - * The description + * A short free text description of the budget source. May be used to provide the title of the budget line, or + * the programme used to fund this project. */ @JsonProperty("description") public String getDescription() { @@ -177,21 +152,17 @@ public String getDescription() { /** * Budget Source *

- * A short free text description of the budget source. May be used to provide the title of the budget line, - * or the programme used to fund this project. - * - * @param description - * The description + * A short free text description of the budget source. May be used to provide the title of the budget line, or + * the programme used to fund this project. */ @JsonProperty("description") - public void setDescription(final String description) { + public void setDescription(String description) { this.description = description; } /** - * - * @return - * The amount + * Value + *

*/ @JsonProperty("amount") public Amount getAmount() { @@ -199,25 +170,21 @@ public Amount getAmount() { } /** - * - * @param amount - * The amount + * Value + *

*/ @JsonProperty("amount") - public void setAmount(final Amount amount) { + public void setAmount(Amount amount) { this.amount = amount; } /** - * Project Title + * Project title *

- * The name of the project that through which this contracting process is funded (if applicable). - * Some organizations maintain a registry of projects, and the data should use the name by which the project - * is known in that registry. No translation option is offered for this string, - * as translated values can be provided in third-party data, linked from the data source above. - * - * @return - * The project + * The name of the project that through which this contracting process is funded (if applicable). Some + * organizations maintain a registry of projects, and the data should use the name by which the project is known + * in that registry. No translation option is offered for this string, as translated values can be provided in + * third-party data, linked from the data source above. */ @JsonProperty("project") public String getProject() { @@ -225,30 +192,24 @@ public String getProject() { } /** - * Project Title + * Project title *

- * The name of the project that through which this contracting process is funded (if applicable). - * Some organizations maintain a registry of projects, and the data should use the name by which the project - * is known in that registry. No translation option is offered for this string, - * as translated values can be provided in third-party data, linked from the data source above. - * - * @param project - * The project + * The name of the project that through which this contracting process is funded (if applicable). Some + * organizations maintain a registry of projects, and the data should use the name by which the project is known + * in that registry. No translation option is offered for this string, as translated values can be provided in + * third-party data, linked from the data source above. */ @JsonProperty("project") - public void setProject(final String project) { + public void setProject(String project) { this.project = project; } /** - * Project Identifier + * Project identifier *

- * An external identifier for the project that this contracting process forms part of, - * or is funded via (if applicable). Some organizations maintain a registry of projects, - * and the data should use the identifier from the relevant registry of projects. - * - * @return - * The projectID + * An external identifier for the project that this contracting process forms part of, or is funded via (if + * applicable). Some organizations maintain a registry of projects, and the data should use the identifier from + * the relevant registry of projects. */ @JsonProperty("projectID") public String getProjectID() { @@ -256,84 +217,110 @@ public String getProjectID() { } /** - * Project Identifier + * Project identifier *

- * An external identifier for the project that this contracting process forms part of, - * or is funded via (if applicable). Some organizations maintain a registry of projects, - * and the data should use the identifier from the relevant registry of projects. + * An external identifier for the project that this contracting process forms part of, or is funded via (if + * applicable). Some organizations maintain a registry of projects, and the data should use the identifier from + * the relevant registry of projects. * - * @param projectID - * The projectID */ @JsonProperty("projectID") - public void setProjectID(final String projectID) { + public void setProjectID(String projectID) { this.projectID = projectID; } /** * Linked budget information *

- * A URI pointing directly to a machine-readable record about the related budget or - * projects for this contracting process. - * - * @return - * The uri + * A URI pointing directly to a machine-readable record about the budget line-item or line-items that fund this + * contracting process. Information may be provided in a range of formats, including using IATI, the Open Fiscal + * Data Standard or any other standard which provides structured data on budget sources. Human readable documents + * can be included using the planning.documents block. */ @JsonProperty("uri") - public String getUri() { + public URI getUri() { return uri; } /** * Linked budget information *

- * A URI pointing directly to a machine-readable record about the related budget or - * projects for this contracting process. - * - * @param uri - * The uri + * A URI pointing directly to a machine-readable record about the budget line-item or line-items that fund this + * contracting process. Information may be provided in a range of formats, including using IATI, the Open Fiscal + * Data Standard or any other standard which provides structured data on budget sources. Human readable documents + * can be included using the planning.documents block. */ @JsonProperty("uri") - public void setUri(final String uri) { + public void setUri(URI uri) { this.uri = uri; } - @Override + /** + * Data Source + *

+ * (Deprecated in 1.1) Used to point either to a corresponding Budget Data Package, or to a machine or + * human-readable source where users can find further information on the budget line item identifiers, or project + * identifiers, provided here. + */ + @JsonProperty("source") + public URI getSource() { + return source; + } + + /** + * Data Source + *

+ * (Deprecated in 1.1) Used to point either to a corresponding Budget Data Package, or to a machine or + * human-readable source where users can find further information on the budget line item identifiers, or project + * identifiers, provided here. + */ + @JsonProperty("source") + public void setSource(URI source) { + this.source = source; + } + + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("id", id) + .append("description", description) + .append("amount", amount) + .append("project", project) + .append("projectID", projectID) + .append("uri", uri) + .append("source", source) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(source). - append(id). - append(description). - append(amount). - append(project). - append(projectID). - append(uri). - toHashCode(); + return new HashCodeBuilder().append(amount) + .append(description) + .append(project) + .append(id) + .append(source) + .append(projectID) + .append(uri) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } - if (!(other instanceof Budget)) { + if ((other instanceof Budget)) { + Budget rhs = ((Budget) other); + return new EqualsBuilder().append(amount, rhs.amount) + .append(description, rhs.description) + .append(project, rhs.project) + .append(id, rhs.id) + .append(source, rhs.source) + .append(projectID, rhs.projectID) + .append(uri, rhs.uri) + .isEquals(); + } else { return false; } - Budget rhs = ((Budget) other); - return new EqualsBuilder(). - append(source, rhs.source). - append(id, rhs.id). - append(description, rhs.description). - append(amount, rhs.amount). - append(project, rhs.project). - append(projectID, rhs.projectID). - append(uri, rhs.uri) - .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Change.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Change.java index 5545e6161..ff3b128e8 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Change.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Change.java @@ -1,15 +1,14 @@ - package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -/** - * http://standard.open-contracting.org/latest/en/schema/reference/#changes - */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "property", "former_value" @@ -17,28 +16,33 @@ public class Change { /** - * The property name that has been changed relative to the place the amendment is. - * For example if the contract value has changed, then the property under changes within - * the contract.amendment would be value.amount. - * + * Property + *

+ * The property name that has been changed relative to the place the amendment is. For example if the contract + * value has changed, then the property under changes within the contract.amendment would be value.amount. + * (Deprecated in 1.1) */ @JsonProperty("property") + @JsonPropertyDescription("The property name that has been changed relative to the place the amendment is. For " + + "example if the contract value has changed, then the property under changes within the contract" + + ".amendment would be value.amount. (Deprecated in 1.1)") private String property; - /** - * The previous value of the changed property, in whatever type the property is. - * + * Former Value + *

+ * The previous value of the changed property, in whatever type the property is. (Deprecated in 1.1) */ @JsonProperty("former_value") + @JsonPropertyDescription("The previous value of the changed property, in whatever type the property is. " + + "(Deprecated in 1.1)") private String formerValue; /** - * The property name that has been changed relative to the place the amendment is. - * For example if the contract value has changed, then the property under changes within - * the contract.amendment would be value.amount. - * - * @return - * The property + * Property + *

+ * The property name that has been changed relative to the place the amendment is. For example if the contract + * value has changed, then the property under changes within the contract.amendment would be value.amount. + * (Deprecated in 1.1) */ @JsonProperty("property") public String getProperty() { @@ -46,23 +50,21 @@ public String getProperty() { } /** - * The property name that has been changed relative to the place the amendment is. - * For example if the contract value has changed, then the property under changes within - * the contract.amendment would be value.amount. - * - * @param property - * The property + * Property + *

+ * The property name that has been changed relative to the place the amendment is. For example if the contract + * value has changed, then the property under changes within the contract.amendment would be value.amount. + * (Deprecated in 1.1) */ @JsonProperty("property") - public void setProperty(final String property) { + public void setProperty(String property) { this.property = property; } /** - * The previous value of the changed property, in whatever type the property is. - * - * @return - * The formerValue + * Former Value + *

+ * The previous value of the changed property, in whatever type the property is. (Deprecated in 1.1) */ @JsonProperty("former_value") public String getFormerValue() { @@ -70,31 +72,30 @@ public String getFormerValue() { } /** - * The previous value of the changed property, in whatever type the property is. - * - * @param formerValue - * The former_value + * Former Value + *

+ * The previous value of the changed property, in whatever type the property is. (Deprecated in 1.1) */ @JsonProperty("former_value") - public void setFormerValue(final String formerValue) { + public void setFormerValue(String formerValue) { this.formerValue = formerValue; } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("property", property) + .append("formerValue", formerValue) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(property). - append(formerValue). - toHashCode(); + return new HashCodeBuilder().append(property).append(formerValue).toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -102,10 +103,9 @@ public boolean equals(final Object other) { return false; } Change rhs = ((Change) other); - return new EqualsBuilder(). - append(property, rhs.property). - append(formerValue, rhs.formerValue). - isEquals(); + return new EqualsBuilder().append(property, rhs.property) + .append(formerValue, rhs.formerValue) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Classification.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Classification.java index be5fe902f..71e60bc0d 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Classification.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Classification.java @@ -1,82 +1,82 @@ package org.devgateway.ocds.persistence.mongo; -import java.io.Serializable; - +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.io.Serializable; +import java.net.URI; + /** - * Classification OCDS Entity http://standard.open-contracting.org/latest/en/schema/reference/#classification + * Classification + *

*/ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "scheme", "id", "description", "uri" }) -@Document public class Classification implements Identifiable { + /** - * An classification should be drawn from an existing scheme or list of codes. - * This field is used to indicate the scheme/codelist from which the classification is drawn. - * For line item classifications, this value should represent an known - * [Item Classification Scheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#item-classification-scheme) - * wherever possible. - * + * Scheme + *

+ * An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the + * scheme/codelist from which the classification is drawn. For line item classifications, this value should + * represent an known [Item Classification Scheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#item-classification-scheme) wherever possible. */ - @ExcelExport @JsonProperty("scheme") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("An classification should be drawn from an existing scheme or list of codes. This field " + + "is used to indicate the scheme/codelist from which the classification is drawn. For line item " + + "classifications, this value should represent an known [Item Classification Scheme](http://standard" + + ".open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) wherever possible.") + @ExcelExport private String scheme; - /** + * ID + *

* The classification code drawn from the selected scheme. - * */ - @ExcelExport @JsonProperty("id") - @Id - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The classification code drawn from the selected scheme.") + @ExcelExport private String id; - /** + * Description + *

* A textual description or title for the code. - * */ - @ExcelExport @JsonProperty("description") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("A textual description or title for the code.") + @ExcelExport private String description; - /** + * URI + *

* A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme * this value should be left blank. - * */ @JsonProperty("uri") - @Merge(MergeStrategy.ocdsVersion) - private String uri; + @JsonPropertyDescription("A URI to identify the code. In the event individual URIs are not available for items in" + + " the identifier scheme this value should be left blank.") + private URI uri; /** - * An classification should be drawn from an existing scheme or list of codes. - * This field is used to indicate the scheme/codelist from which the classification is drawn. - * For line item classifications, this value should represent an known - * [Item Classification Scheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#item-classification-scheme) - * wherever possible. - * - * @return - * The scheme + * Scheme + *

+ * An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the + * scheme/codelist from which the classification is drawn. For line item classifications, this value should + * represent an known [Item Classification Scheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#item-classification-scheme) wherever possible. */ @JsonProperty("scheme") public String getScheme() { @@ -84,26 +84,22 @@ public String getScheme() { } /** - * An classification should be drawn from an existing scheme or list of codes. - * This field is used to indicate the scheme/codelist from which the classification is drawn. - * For line item classifications, this value should represent an known - * [Item Classification Scheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#item-classification-scheme) - * wherever possible. - * - * @param scheme - * The scheme + * Scheme + *

+ * An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the + * scheme/codelist from which the classification is drawn. For line item classifications, this value should + * represent an known [Item Classification Scheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#item-classification-scheme) wherever possible. */ @JsonProperty("scheme") - public void setScheme(final String scheme) { + public void setScheme(String scheme) { this.scheme = scheme; } /** + * ID + *

* The classification code drawn from the selected scheme. - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -111,21 +107,19 @@ public String getId() { } /** + * ID + *

* The classification code drawn from the selected scheme. - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** + * Description + *

* A textual description or title for the code. - * - * @return - * The description */ @JsonProperty("description") public String getDescription() { @@ -133,57 +127,58 @@ public String getDescription() { } /** + * Description + *

* A textual description or title for the code. - * - * @param description - * The description */ @JsonProperty("description") - public void setDescription(final String description) { + public void setDescription(String description) { this.description = description; } /** + * URI + *

* A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme * this value should be left blank. - * - * @return - * The uri */ @JsonProperty("uri") - public String getUri() { + public URI getUri() { return uri; } /** + * URI + *

* A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme * this value should be left blank. - * - * @param uri - * The uri */ @JsonProperty("uri") - public void setUri(final String uri) { + public void setUri(URI uri) { this.uri = uri; } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("scheme", scheme) + .append("id", id) + .append("description", description) + .append("uri", uri) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(scheme). - append(id). - append(description). - append(uri). - toHashCode(); + return new HashCodeBuilder().append(description) + .append(id) + .append(scheme) + .append(uri) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -191,11 +186,11 @@ public boolean equals(final Object other) { return false; } Classification rhs = ((Classification) other); - return new EqualsBuilder(). - append(scheme, rhs.scheme). - append(id, rhs.id). - append(description, rhs.description). - append(uri, rhs.uri).isEquals(); + return new EqualsBuilder().append(description, rhs.description) + .append(id, rhs.id) + .append(scheme, rhs.scheme) + .append(uri, rhs.uri) + .isEquals(); } @Override diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ContactPoint.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ContactPoint.java index a31b5e860..e6f93fd1f 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ContactPoint.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ContactPoint.java @@ -1,21 +1,23 @@ package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.net.URI; /** + * Contact point + *

* An person, contact point or department to contact in relation to this contracting process. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#contactpoint */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "name", "email", @@ -26,57 +28,60 @@ public class ContactPoint { /** - * The name of the contact person, department, or contact point, - * for correspondence relating to this contracting process. - * + * Name + *

+ * The name of the contact person, department, or contact point, for correspondence relating to this contracting + * process. */ - @ExcelExport @JsonProperty("name") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The name of the contact person, department, or contact point, for correspondence " + + "relating to this contracting process.") private String name; - /** + * Email + *

* The e-mail address of the contact point/person. - * */ - @ExcelExport @JsonProperty("email") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The e-mail address of the contact point/person.") private String email; - /** - * The telephone number of the contact point/person. This should include the international dialling code. - * + * Telephone + *

+ * The telephone number of the contact point/person. This should include the international dialing code. */ - @ExcelExport @JsonProperty("telephone") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The telephone number of the contact point/person. This should include the international" + + " dialing code.") private String telephone; - /** - * The fax number of the contact point/person. This should include the international dialling code. - * + * Fax number + *

+ * The fax number of the contact point/person. This should include the international dialing code. */ - @ExcelExport @JsonProperty("faxNumber") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The fax number of the contact point/person. This should include the international " + + "dialing code.") private String faxNumber; - /** + * URL + *

* A web address for the contact point/person. - * */ - @ExcelExport @JsonProperty("url") - @Merge(MergeStrategy.ocdsVersion) - private String url; + @ExcelExport + @JsonPropertyDescription("A web address for the contact point/person.") + private URI url; /** - * The name of the contact person, department, or contact point, - * for correspondence relating to this contracting process. - * - * @return - * The name + * Name + *

+ * The name of the contact person, department, or contact point, for correspondence relating to this contracting + * process. */ @JsonProperty("name") public String getName() { @@ -84,22 +89,20 @@ public String getName() { } /** - * The name of the contact person, department, or contact point, - * for correspondence relating to this contracting process. - * - * @param name - * The name + * Name + *

+ * The name of the contact person, department, or contact point, for correspondence relating to this contracting + * process. */ @JsonProperty("name") - public void setName(final String name) { + public void setName(String name) { this.name = name; } /** + * Email + *

* The e-mail address of the contact point/person. - * - * @return - * The email */ @JsonProperty("email") public String getEmail() { @@ -107,21 +110,19 @@ public String getEmail() { } /** + * Email + *

* The e-mail address of the contact point/person. - * - * @param email - * The email */ @JsonProperty("email") - public void setEmail(final String email) { + public void setEmail(String email) { this.email = email; } /** - * The telephone number of the contact point/person. This should include the international dialling code. - * - * @return - * The telephone + * Telephone + *

+ * The telephone number of the contact point/person. This should include the international dialing code. */ @JsonProperty("telephone") public String getTelephone() { @@ -129,21 +130,19 @@ public String getTelephone() { } /** - * The telephone number of the contact point/person. This should include the international dialling code. - * - * @param telephone - * The telephone + * Telephone + *

+ * The telephone number of the contact point/person. This should include the international dialing code. */ @JsonProperty("telephone") - public void setTelephone(final String telephone) { + public void setTelephone(String telephone) { this.telephone = telephone; } /** - * The fax number of the contact point/person. This should include the international dialling code. - * - * @return - * The faxNumber + * Fax number + *

+ * The fax number of the contact point/person. This should include the international dialing code. */ @JsonProperty("faxNumber") public String getFaxNumber() { @@ -151,56 +150,58 @@ public String getFaxNumber() { } /** - * The fax number of the contact point/person. This should include the international dialling code. - * - * @param faxNumber - * The faxNumber + * Fax number + *

+ * The fax number of the contact point/person. This should include the international dialing code. */ @JsonProperty("faxNumber") - public void setFaxNumber(final String faxNumber) { + public void setFaxNumber(String faxNumber) { this.faxNumber = faxNumber; } /** + * URL + *

* A web address for the contact point/person. - * - * @return - * The url */ @JsonProperty("url") - public String getUrl() { + public URI getUrl() { return url; } /** + * URL + *

* A web address for the contact point/person. - * - * @param url - * The url */ @JsonProperty("url") - public void setUrl(final String url) { + public void setUrl(URI url) { this.url = url; } @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("name", name) + .append("email", email) + .append("telephone", telephone) + .append("faxNumber", faxNumber) + .append("url", url) + + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(name). - append(email). - append(telephone). - append(faxNumber). - append(url). - toHashCode(); + return new HashCodeBuilder().append(name) + .append(faxNumber) + .append(telephone) + .append(email) + .append(url) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -208,12 +209,12 @@ public boolean equals(final Object other) { return false; } ContactPoint rhs = ((ContactPoint) other); - return new EqualsBuilder(). - append(name, rhs.name). - append(email, rhs.email). - append(telephone, rhs.telephone). - append(faxNumber, rhs.faxNumber). - append(url, rhs.url). - isEquals(); + return new EqualsBuilder().append(name, rhs.name) + .append(faxNumber, rhs.faxNumber) + .append(telephone, rhs.telephone) + .append(email, rhs.email) + .append(url, rhs.url) + .isEquals(); } + } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Contract.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Contract.java index 0e4107a8e..e941c0e97 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Contract.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Contract.java @@ -1,7 +1,9 @@ package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -10,13 +12,12 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExportSepareteSheet; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import java.io.Serializable; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -25,10 +26,8 @@ * Contract *

* Information regarding the signed contract between the buyer and supplier(s). - * - * http://standard.open-contracting.org/latest/en/schema/reference/#contract - * */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "awardID", @@ -40,145 +39,174 @@ "items", "dateSigned", "documents", - "amendment", - "implementation" + "implementation", + "relatedProcesses", + "milestones", + "amendments", + "amendment" }) -public class Contract implements Identifiable { +public class Contract { /** * Contract ID *

* The identifier for this contract. It must be unique and cannot change within its Open Contracting Process - * (defined by a single ocid). See the - * [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) - * for further details. + * (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting + * .org/latest/en/schema/identifiers/) for further details. * (Required) - * */ - @ExcelExport @JsonProperty("id") - @Merge(MergeStrategy.overwrite) + @ExcelExport + @JsonPropertyDescription("The identifier for this contract. It must be unique and cannot change within its Open " + + "Contracting Process (defined by a single ocid). See the [identifier guidance](http://standard" + + ".open-contracting.org/latest/en/schema/identifiers/) for further details.") private String id; - /** * Award ID *

* The award.id against which this contract is being issued. * (Required) - * */ - @ExcelExport @JsonProperty("awardID") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The award.id against which this contract is being issued.") private String awardID; - /** * Contract title - * + *

+ * Contract title */ - @ExcelExport @JsonProperty("title") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("Contract title") private String title; - /** * Contract description - * + *

+ * Contract description */ - @ExcelExport @JsonProperty("description") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("Contract description") private String description; - /** - * Contract Status + * Contract status *

- * The current status of the contract. Drawn from the - * [contractStatus codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#contract-status) - * + * The current status of the contract. Drawn from the [contractStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#contract-status) */ - @ExcelExport @JsonProperty("status") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The current status of the contract. Drawn from the [contractStatus codelist]" + + "(http://standard.open-contracting.org/latest/en/schema/codelists/#contract-status)") private Status status; - /** * Period *

- * - * */ - @ExcelExport @JsonProperty("period") - private Period period; - @ExcelExport + @JsonPropertyDescription(" ") + private Period period; + /** + * Value + *

+ */ @JsonProperty("value") + @ExcelExport private Amount value; - /** - * Items Contracted + * Items contracted *

- * The goods, services, and any intangible outcomes in this contract. - * Note: If the items are the same as the award do not repeat. - * + * The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the + * award do not repeat. */ + @JsonProperty("items") + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("The goods, services, and any intangible outcomes in this contract. Note: If the items " + + "are the same as the award do not repeat.") @ExcelExport @ExcelExportSepareteSheet - @JsonProperty("items") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) private Set items = new LinkedHashSet(); - /** + * Date signed + *

* The date the contract was signed. In the case of multiple signatures, the date of the last signature. - * */ - @ExcelExport @JsonProperty("dateSigned") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The date the contract was signed. In the case of multiple signatures, the date of the " + + "last signature.") private Date dateSigned; - /** + * Documents + *

* All documents and attachments related to the contract, including any notices. - * */ @JsonProperty("documents") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("All documents and attachments related to the contract, including any notices.") private Set documents = new LinkedHashSet(); - - /** - * Amendment information - *

- * - * - */ - @JsonProperty("amendment") - protected Amendment amendment; - /** * Implementation *

* Information during the performance / implementation stage of the contract. - * */ - @ExcelExport @JsonProperty("implementation") + @ExcelExport + @JsonPropertyDescription("Information during the performance / implementation stage of the contract.") private Implementation implementation; + /** + * Related processes + *

+ * If this process is followed by one or more contracting processes, represented under a separate open + * contracting identifier (ocid) then details of the related process can be provided here. This is commonly used + * to point to subcontracts, or to renewal and replacement processes for this contract. + */ + @JsonProperty("relatedProcesses") + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("If this process is followed by one or more contracting processes, represented under a " + + "separate open contracting identifier (ocid) then details of the related process can be provided here. " + + "This is commonly used to point to subcontracts, or to renewal and replacement processes for this " + + "contract.") + private Set relatedProcesses = new LinkedHashSet(); + /** + * Contract milestones + *

+ * A list of milestones associated with the finalization of this contract. + */ + @JsonProperty("milestones") + @JsonPropertyDescription("A list of milestones associated with the finalization of this contract.") + private List milestones = new ArrayList(); + /** + * Amendments + *

+ * A contract amendment is a formal change to, or extension of, a contract, and generally involves the + * publication of a new contract notice/release, or some other documents detailing the change. The rationale and + * a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + @JsonPropertyDescription("A contract amendment is a formal change to, or extension of, a contract, and generally " + + "involves the publication of a new contract notice/release, or some other documents detailing the " + + "change. The rationale and a description of the changes made can be provided here.") + private List amendments = new ArrayList(); + /** + * Amendment + *

+ * Amendment information + */ + @JsonProperty("amendment") + @JsonPropertyDescription("Amendment information") + private Amendment amendment; + /** * Contract ID *

* The identifier for this contract. It must be unique and cannot change within its Open Contracting Process - * (defined by a single ocid). See the - * [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) - * for further details. + * (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting + * .org/latest/en/schema/identifiers/) for further details. * (Required) - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -189,16 +217,12 @@ public String getId() { * Contract ID *

* The identifier for this contract. It must be unique and cannot change within its Open Contracting Process - * (defined by a single ocid). See the - * [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) - * for further details. + * (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting + * .org/latest/en/schema/identifiers/) for further details. * (Required) - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } @@ -207,9 +231,6 @@ public void setId(final String id) { *

* The award.id against which this contract is being issued. * (Required) - * - * @return - * The awardID */ @JsonProperty("awardID") public String getAwardID() { @@ -221,20 +242,16 @@ public String getAwardID() { *

* The award.id against which this contract is being issued. * (Required) - * - * @param awardID - * The awardID */ @JsonProperty("awardID") - public void setAwardID(final String awardID) { + public void setAwardID(String awardID) { this.awardID = awardID; } /** * Contract title - * - * @return - * The title + *

+ * Contract title */ @JsonProperty("title") public String getTitle() { @@ -243,20 +260,18 @@ public String getTitle() { /** * Contract title - * - * @param title - * The title + *

+ * Contract title */ @JsonProperty("title") - public void setTitle(final String title) { + public void setTitle(String title) { this.title = title; } /** * Contract description - * - * @return - * The description + *

+ * Contract description */ @JsonProperty("description") public String getDescription() { @@ -265,24 +280,19 @@ public String getDescription() { /** * Contract description - * - * @param description - * The description + *

+ * Contract description */ @JsonProperty("description") - public void setDescription(final String description) { + public void setDescription(String description) { this.description = description; } /** - * Contract Status + * Contract status *

- * The current status of the contract. Drawn from the - * [contractStatus codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#contract-status) - * - * @return - * The status + * The current status of the contract. Drawn from the [contractStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#contract-status) */ @JsonProperty("status") public Status getStatus() { @@ -290,27 +300,19 @@ public Status getStatus() { } /** - * Contract Status + * Contract status *

- * The current status of the contract. Drawn from the - * [contractStatus codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#contract-status) - * - * @param status - * The status + * The current status of the contract. Drawn from the [contractStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#contract-status) */ @JsonProperty("status") - public void setStatus(final Status status) { + public void setStatus(Status status) { this.status = status; } /** * Period *

- * - * - * @return - * The period */ @JsonProperty("period") public Period getPeriod() { @@ -320,20 +322,15 @@ public Period getPeriod() { /** * Period *

- * - * - * @param period - * The period */ @JsonProperty("period") - public void setPeriod(final Period period) { + public void setPeriod(Period period) { this.period = period; } /** - * - * @return - * The value + * Value + *

*/ @JsonProperty("value") public Amount getValue() { @@ -341,23 +338,19 @@ public Amount getValue() { } /** - * - * @param value - * The value + * Value + *

*/ @JsonProperty("value") - public void setValue(final Amount value) { + public void setValue(Amount value) { this.value = value; } /** - * Items Contracted + * Items contracted *

- * The goods, services, and any intangible outcomes in this contract. - * Note: If the items are the same as the award do not repeat. - * - * @return - * The items + * The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the + * award do not repeat. */ @JsonProperty("items") public Set getItems() { @@ -365,24 +358,20 @@ public Set getItems() { } /** - * Items Contracted + * Items contracted *

- * The goods, services, and any intangible outcomes in this contract. - * Note: If the items are the same as the award do not repeat. - * - * @param items - * The items + * The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the + * award do not repeat. */ @JsonProperty("items") - public void setItems(final Set items) { + public void setItems(Set items) { this.items = items; } /** + * Date signed + *

* The date the contract was signed. In the case of multiple signatures, the date of the last signature. - * - * @return - * The dateSigned */ @JsonProperty("dateSigned") public Date getDateSigned() { @@ -390,21 +379,19 @@ public Date getDateSigned() { } /** + * Date signed + *

* The date the contract was signed. In the case of multiple signatures, the date of the last signature. - * - * @param dateSigned - * The dateSigned */ @JsonProperty("dateSigned") - public void setDateSigned(final Date dateSigned) { + public void setDateSigned(Date dateSigned) { this.dateSigned = dateSigned; } /** + * Documents + *

* All documents and attachments related to the contract, including any notices. - * - * @return - * The documents */ @JsonProperty("documents") public Set getDocuments() { @@ -412,93 +399,167 @@ public Set getDocuments() { } /** + * Documents + *

* All documents and attachments related to the contract, including any notices. - * - * @param documents - * The documents */ @JsonProperty("documents") - public void setDocuments(final Set documents) { + public void setDocuments(Set documents) { this.documents = documents; } /** - * Amendment information + * Implementation *

- * - * - * @return - * The amendment + * Information during the performance / implementation stage of the contract. */ - @JsonProperty("amendment") - public Amendment getAmendment() { - return amendment; + @JsonProperty("implementation") + public Implementation getImplementation() { + return implementation; } /** - * Amendment information + * Implementation *

- * - * - * @param amendment - * The amendment + * Information during the performance / implementation stage of the contract. */ - @JsonProperty("amendment") - public void setAmendment(final Amendment amendment) { - this.amendment = amendment; + @JsonProperty("implementation") + public void setImplementation(Implementation implementation) { + this.implementation = implementation; } /** - * Implementation + * Related processes *

- * Information during the performance / implementation stage of the contract. - * - * @return - * The implementation + * If this process is followed by one or more contracting processes, represented under a separate open + * contracting identifier (ocid) then details of the related process can be provided here. This is commonly used + * to point to subcontracts, or to renewal and replacement processes for this contract. */ - @JsonProperty("implementation") - public Implementation getImplementation() { - return implementation; + @JsonProperty("relatedProcesses") + public Set getRelatedProcesses() { + return relatedProcesses; } /** - * Implementation + * Related processes *

- * Information during the performance / implementation stage of the contract. - * - * @param implementation - * The implementation + * If this process is followed by one or more contracting processes, represented under a separate open + * contracting identifier (ocid) then details of the related process can be provided here. This is commonly used + * to point to subcontracts, or to renewal and replacement processes for this contract. */ - @JsonProperty("implementation") - public void setImplementation(final Implementation implementation) { - this.implementation = implementation; + @JsonProperty("relatedProcesses") + public void setRelatedProcesses(Set relatedProcesses) { + this.relatedProcesses = relatedProcesses; + } + + /** + * Contract milestones + *

+ * A list of milestones associated with the finalization of this contract. + */ + @JsonProperty("milestones") + public List getMilestones() { + return milestones; } + /** + * Contract milestones + *

+ * A list of milestones associated with the finalization of this contract. + */ + @JsonProperty("milestones") + public void setMilestones(List milestones) { + this.milestones = milestones; + } + + /** + * Amendments + *

+ * A contract amendment is a formal change to, or extension of, a contract, and generally involves the + * publication of a new contract notice/release, or some other documents detailing the change. The rationale and + * a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + public List getAmendments() { + return amendments; + } + + /** + * Amendments + *

+ * A contract amendment is a formal change to, or extension of, a contract, and generally involves the + * publication of a new contract notice/release, or some other documents detailing the change. The rationale and + * a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + public void setAmendments(List amendments) { + this.amendments = amendments; + } + + /** + * Amendment + *

+ * Amendment information + */ + @JsonProperty("amendment") + public Amendment getAmendment() { + return amendment; + } + + /** + * Amendment + *

+ * Amendment information + */ + @JsonProperty("amendment") + public void setAmendment(Amendment amendment) { + this.amendment = amendment; + } + + + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("id", id) + .append("awardID", awardID) + .append("title", title) + .append("description", description) + .append("status", status) + .append("period", period) + .append("value", value) + .append("items", items) + .append("dateSigned", dateSigned) + .append("documents", documents) + .append("implementation", implementation) + .append("relatedProcesses", relatedProcesses) + .append("milestones", milestones) + .append("amendments", amendments) + .append("amendment", amendment) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(id). - append(awardID). - append(title). - append(description). - append(status). - append(period). - append(value). - append(items). - append(dateSigned). - append(documents). - append(amendment). - append(implementation). - toHashCode(); + return new HashCodeBuilder().append(awardID) + .append(amendment) + .append(period) + .append(documents) + .append(relatedProcesses) + .append(implementation) + .append(description) + .append(amendments) + .append(title) + .append(id) + .append(dateSigned) + .append(milestones) + .append(value) + .append(items) + .append(status) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -506,53 +567,55 @@ public boolean equals(final Object other) { return false; } Contract rhs = ((Contract) other); - return new EqualsBuilder(). - append(id, rhs.id). - append(awardID, rhs.awardID). - append(title, rhs.title). - append(description, rhs.description). - append(status, rhs.status). - append(period, rhs.period). - append(value, rhs.value). - append(items, rhs.items). - append(dateSigned, rhs.dateSigned). - append(documents, rhs.documents). - append(amendment, rhs.amendment). - append(implementation, rhs.implementation). - isEquals(); + return new EqualsBuilder().append(awardID, rhs.awardID) + .append(amendment, rhs.amendment) + .append(period, rhs.period) + .append(documents, rhs.documents) + .append(relatedProcesses, rhs.relatedProcesses) + .append(implementation, rhs.implementation) + .append(description, rhs.description) + .append(amendments, rhs.amendments) + .append(title, rhs.title) + .append(id, rhs.id) + .append(dateSigned, rhs.dateSigned) + .append(milestones, rhs.milestones) + .append(value, rhs.value) + .append(items, rhs.items) + .append(status, rhs.status) + .isEquals(); } public enum Status { - pending("pending"), + pending("pending"), active("active"), - cancelled("cancelled"), - terminated("terminated"); - private final String value; - private static final Map CONSTANTS = new HashMap(); static { - for (Contract.Status c: values()) { + for (Status c : values()) { CONSTANTS.put(c.value, c); } } - Status(final String value) { + Status(String value) { this.value = value; } - @JsonValue @Override public String toString() { return this.value; } + @JsonValue + public String value() { + return this.value; + } + @JsonCreator - public static Status fromValue(final String value) { + public static Status fromValue(String value) { Status constant = CONSTANTS.get(value); if (constant == null) { throw new IllegalArgumentException(value); @@ -560,10 +623,7 @@ public static Status fromValue(final String value) { return constant; } } - } - @Override - public Serializable getIdProperty() { - return id; } + } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Detail.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Detail.java index 27db50923..ba29ab673 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Detail.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Detail.java @@ -1,8 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; @@ -15,9 +12,7 @@ import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; import java.util.Date; -import java.util.HashMap; import java.util.LinkedHashSet; -import java.util.Map; import java.util.Set; @@ -85,8 +80,6 @@ public class Detail { @JsonDeserialize(as = LinkedHashSet.class) @JsonPropertyDescription("All documents and attachments related to the bid and its evaluation.") private Set documents = new LinkedHashSet(); - @JsonIgnore - private Map additionalProperties = new HashMap(); /** * ID @@ -207,24 +200,12 @@ public String toString() { return ToStringBuilder.reflectionToString(this); } - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @JsonAnySetter - public void setAdditionalProperty(String name, Object value) { - this.additionalProperties.put(name, value); - } - public void setAdditionalProperties(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } @Override public int hashCode() { return new HashCodeBuilder().append(id).append(date).append(status).append(tenderers).append(value) - .append(documents).append(additionalProperties).toHashCode(); + .append(documents).toHashCode(); } @Override @@ -237,8 +218,8 @@ public boolean equals(Object other) { } Detail rhs = ((Detail) other); return new EqualsBuilder().append(id, rhs.id).append(date, rhs.date).append(status, rhs.status) - .append(tenderers, rhs.tenderers).append(value, rhs.value).append(documents, rhs.documents) - .append(additionalProperties, rhs.additionalProperties).isEquals(); + .append(tenderers, rhs.tenderers).append(value, rhs.value).append(documents, rhs.documents + ).isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Details.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Details.java new file mode 100644 index 000000000..d57ff2227 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Details.java @@ -0,0 +1,45 @@ +package org.devgateway.ocds.persistence.mongo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + + +/** + * Details + *

+ * Additional classification information about parties can be provided using partyDetail extensions that define + * particular properties and classification schemes. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + +}) +public class Details { + + + @Override + public String toString() { + return new ToStringBuilder(this).toString(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder().toHashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Details)) { + return false; + } + Details rhs = ((Details) other); + return new EqualsBuilder().isEquals(); + } + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Document.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Document.java index 7d8c1ec6f..0c563fba1 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Document.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Document.java @@ -1,28 +1,25 @@ package org.devgateway.ocds.persistence.mongo; -import java.io.Serializable; -import java.util.Date; - +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.net.URI; +import java.util.Date; /** * Document *

* Links to, or descriptions of, external documents can be attached at various locations within the standard. - * Documents may be supporting information, formal notices, downloadable forms, - * or any other kind of resource that should be made public as part of full open contracting. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#document - * + * Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that + * should be made public as part of full open contracting. */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "documentType", @@ -34,104 +31,117 @@ "format", "language" }) -public class Document implements Identifiable { +public class Document { /** - * A local, unique identifier for this document. This field is used to keep track of multiple revisions - * of a document through the compilation from release to record mechanism. + * ID + *

+ * A local, unique identifier for this document. This field is used to keep track of multiple revisions of a + * document through the compilation from release to record mechanism. * (Required) - * */ @JsonProperty("id") - @Merge(MergeStrategy.overwrite) + @JsonPropertyDescription("A local, unique identifier for this document. This field is used to keep track of " + + "multiple revisions of a document through the compilation from release to record mechanism.") private String id; - /** - * A classification of the document described taken from the - * [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type). - * Values from the provided codelist should be used wherever possible, - * though extended values can be provided if the codelist does not have a relevant code. - * + * Document type + *

+ * A classification of the document described taken from the [documentType codelist](http://standard + * .open-contracting.org/latest/en/schema/codelists/#document-type). Values from the provided codelist should be + * used wherever possible, though extended values can be provided if the codelist does not have a relevant code. */ @JsonProperty("documentType") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("A classification of the document described taken from the [documentType codelist]" + + "(http://standard.open-contracting.org/latest/en/schema/codelists/#document-type). Values from the " + + "provided codelist should be used wherever possible, though extended values can be provided if the " + + "codelist does not have a relevant code.") private String documentType; - /** + * Title + *

* The document title. - * */ @JsonProperty("title") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The document title.") private String title; - /** - * A short description of the document. We recommend descriptions do not exceed 250 words. - * In the event the document is not accessible online, the description field can be used to describe arrangements - * for obtaining a copy of the document. - * + * Description + *

+ * A short description of the document. We recommend descriptions do not exceed 250 words. In the event the + * document is not accessible online, the description field can be used to describe arrangements for obtaining a + * copy of the document. */ @JsonProperty("description") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("A short description of the document. We recommend descriptions do not exceed 250 words." + + " In the event the document is not accessible online, the description field can be used to describe " + + "arrangements for obtaining a copy of the document.") private String description; - /** - * direct link to the document or attachment. The server providing access to this document should be configured - * to correctly report the document mime type. - * + * URL + *

+ * direct link to the document or attachment. The server providing access to this document should be configured + * to correctly report the document mime type. */ @JsonProperty("url") - @Merge(MergeStrategy.ocdsVersion) - private String url; - + @JsonPropertyDescription(" direct link to the document or attachment. The server providing access to this " + + "document should be configured to correctly report the document mime type.") + private URI url; /** - * The date on which the document was first published. This is particularly important for legally - * important documents such as notices of a tender. - * + * Date published + *

+ * The date on which the document was first published. This is particularly important for legally important + * documents such as notices of a tender. */ @JsonProperty("datePublished") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The date on which the document was first published. This is particularly important for " + + "legally important documents such as notices of a tender.") private Date datePublished; - /** + * Date modified + *

* Date that the document was last modified - * */ @JsonProperty("dateModified") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Date that the document was last modified") private Date dateModified; - /** - * The format of the document taken from the - * [IANA Media Types code list](http://www.iana.org/assignments/media-types/), - * with the addition of one extra value for 'offline/print', used when this document entry is being used - * to describe the offline publication of a document. Use values from the template column. - * Links to web pages should be tagged 'text/html'. - * + * Format + *

+ * The format of the document taken from the [IANA Media Types codelist](http://www.iana + * .org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this + * document entry is being used to describe the offline publication of a document. Use values from the template + * column. Links to web pages should be tagged 'text/html'. */ @JsonProperty("format") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The format of the document taken from the [IANA Media Types codelist](http://www.iana" + + ".org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when " + + "this document entry is being used to describe the offline publication of a document. Use values from " + + "the template column. Links to web pages should be tagged 'text/html'.") private String format; - /** - * Specifies the language of the linked document using either two-digit - * [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), o - * r extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). - * The use of two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) - * is strongly recommended unless there is a clear user need for distinguishing the language subtype. - * + * Language + *

+ * Specifies the language of the linked document using either two-letter [ISO639-1](https://en.wikipedia + * .org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www + * .w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1] + * (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user + * need for distinguishing the language subtype. */ @JsonProperty("language") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Specifies the language of the linked document using either two-letter [ISO639-1]" + + "(https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www" + + ".w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1]" + + "(https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a " + + "clear user need for distinguishing the language subtype.") private String language; /** - * A local, unique identifier for this document. This field is used to keep track of multiple revisions of - * a document through the compilation from release to record mechanism. + * ID + *

+ * A local, unique identifier for this document. This field is used to keep track of multiple revisions of a + * document through the compilation from release to record mechanism. * (Required) - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -139,26 +149,23 @@ public String getId() { } /** - * A local, unique identifier for this document. This field is used to keep track of multiple revisions of - * a document through the compilation from release to record mechanism. + * ID + *

+ * A local, unique identifier for this document. This field is used to keep track of multiple revisions of a + * document through the compilation from release to record mechanism. * (Required) - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** - * A classification of the document described taken from the - * [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type). - * Values from the provided codelist should be used wherever possible, though extended values can be provided if - * the codelist does not have a relevant code. - * - * @return - * The documentType + * Document type + *

+ * A classification of the document described taken from the [documentType codelist](http://standard + * .open-contracting.org/latest/en/schema/codelists/#document-type). Values from the provided codelist should be + * used wherever possible, though extended values can be provided if the codelist does not have a relevant code. */ @JsonProperty("documentType") public String getDocumentType() { @@ -166,24 +173,21 @@ public String getDocumentType() { } /** - * A classification of the document described taken from the - * [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type). - * Values from the provided codelist should be used wherever possible, though extended values can be provided if - * the codelist does not have a relevant code. - * - * @param documentType - * The documentType + * Document type + *

+ * A classification of the document described taken from the [documentType codelist](http://standard + * .open-contracting.org/latest/en/schema/codelists/#document-type). Values from the provided codelist should be + * used wherever possible, though extended values can be provided if the codelist does not have a relevant code. */ @JsonProperty("documentType") - public void setDocumentType(final String documentType) { + public void setDocumentType(String documentType) { this.documentType = documentType; } /** + * Title + *

* The document title. - * - * @return - * The title */ @JsonProperty("title") public String getTitle() { @@ -191,23 +195,21 @@ public String getTitle() { } /** + * Title + *

* The document title. - * - * @param title - * The title */ @JsonProperty("title") - public void setTitle(final String title) { + public void setTitle(String title) { this.title = title; } /** - * A short description of the document. We recommend descriptions do not exceed 250 words. - * In the event the document is not accessible online, the description field can be used to describe arrangements - * for obtaining a copy of the document. - * - * @return - * The description + * Description + *

+ * A short description of the document. We recommend descriptions do not exceed 250 words. In the event the + * document is not accessible online, the description field can be used to describe arrangements for obtaining a + * copy of the document. */ @JsonProperty("description") public String getDescription() { @@ -215,48 +217,44 @@ public String getDescription() { } /** - * A short description of the document. We recommend descriptions do not exceed 250 words. - * In the event the document is not accessible online, the description field can be used to describe arrangements - * for obtaining a copy of the document. - * - * @param description - * The description + * Description + *

+ * A short description of the document. We recommend descriptions do not exceed 250 words. In the event the + * document is not accessible online, the description field can be used to describe arrangements for obtaining a + * copy of the document. */ @JsonProperty("description") - public void setDescription(final String description) { + public void setDescription(String description) { this.description = description; } /** - * direct link to the document or attachment. The server providing access to this document should be configured - * to correctly report the document mime type. - * - * @return - * The url + * URL + *

+ * direct link to the document or attachment. The server providing access to this document should be configured + * to correctly report the document mime type. */ @JsonProperty("url") - public String getUrl() { + public URI getUrl() { return url; } /** - * direct link to the document or attachment. The server providing access to this document should be configured - * to correctly report the document mime type. - * - * @param url - * The url + * URL + *

+ * direct link to the document or attachment. The server providing access to this document should be configured + * to correctly report the document mime type. */ @JsonProperty("url") - public void setUrl(final String url) { + public void setUrl(URI url) { this.url = url; } /** + * Date published + *

* The date on which the document was first published. This is particularly important for legally important * documents such as notices of a tender. - * - * @return - * The datePublished */ @JsonProperty("datePublished") public Date getDatePublished() { @@ -264,22 +262,20 @@ public Date getDatePublished() { } /** + * Date published + *

* The date on which the document was first published. This is particularly important for legally important * documents such as notices of a tender. - * - * @param datePublished - * The datePublished */ @JsonProperty("datePublished") - public void setDatePublished(final Date datePublished) { + public void setDatePublished(Date datePublished) { this.datePublished = datePublished; } /** + * Date modified + *

* Date that the document was last modified - * - * @return - * The dateModified */ @JsonProperty("dateModified") public Date getDateModified() { @@ -287,25 +283,22 @@ public Date getDateModified() { } /** + * Date modified + *

* Date that the document was last modified - * - * @param dateModified - * The dateModified */ @JsonProperty("dateModified") - public void setDateModified(final Date dateModified) { + public void setDateModified(Date dateModified) { this.dateModified = dateModified; } /** - * The format of the document taken from the - * [IANA Media Types code list](http://www.iana.org/assignments/media-types/), - * with the addition of one extra value for 'offline/print', used when this document entry is being used to - * describe the offline publication of a document. Use values from the template column. - * Links to web pages should be tagged 'text/html'. - * - * @return - * The format + * Format + *

+ * The format of the document taken from the [IANA Media Types codelist](http://www.iana + * .org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this + * document entry is being used to describe the offline publication of a document. Use values from the template + * column. Links to web pages should be tagged 'text/html'. */ @JsonProperty("format") public String getFormat() { @@ -313,29 +306,26 @@ public String getFormat() { } /** - * The format of the document taken from the - * [IANA Media Types code list](http://www.iana.org/assignments/media-types/), - * with the addition of one extra value for 'offline/print', used when this document entry is being used to - * describe the offline publication of a document. Use values from the template column. - * Links to web pages should be tagged 'text/html'. - * - * @param format - * The format + * Format + *

+ * The format of the document taken from the [IANA Media Types codelist](http://www.iana + * .org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this + * document entry is being used to describe the offline publication of a document. Use values from the template + * column. Links to web pages should be tagged 'text/html'. */ @JsonProperty("format") - public void setFormat(final String format) { + public void setFormat(String format) { this.format = format; } /** - * Specifies the language of the linked document using either two-digit - * [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), - * or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). - * The use of two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) - * is strongly recommended unless there is a clear user need for distinguishing the language subtype. - * - * @return - * The language + * Language + *

+ * Specifies the language of the linked document using either two-letter [ISO639-1](https://en.wikipedia + * .org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www + * .w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1] + * (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user + * need for distinguishing the language subtype. */ @JsonProperty("language") public String getLanguage() { @@ -343,42 +333,50 @@ public String getLanguage() { } /** - * Specifies the language of the linked document using either two-digit - * [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), - * or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). - * The use of two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) - * is strongly recommended unless there is a clear user need for distinguishing the language subtype. - * - * @param language - * The language + * Language + *

+ * Specifies the language of the linked document using either two-letter [ISO639-1](https://en.wikipedia + * .org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www + * .w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1] + * (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user + * need for distinguishing the language subtype. */ @JsonProperty("language") - public void setLanguage(final String language) { + public void setLanguage(String language) { this.language = language; } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("id", id) + .append("documentType", documentType) + .append("title", title) + .append("description", description) + .append("url", url) + .append("datePublished", datePublished) + .append("dateModified", dateModified) + .append("format", format) + .append("language", language) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(id). - append(documentType). - append(title). - append(description). - append(url). - append(datePublished). - append(dateModified). - append(format). - append(language). - toHashCode(); + return new HashCodeBuilder().append(datePublished) + .append(documentType) + .append(format) + .append(description) + .append(dateModified) + .append(language) + .append(id) + .append(title) + .append(url) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -386,22 +384,16 @@ public boolean equals(final Object other) { return false; } Document rhs = ((Document) other); - return new EqualsBuilder(). - append(id, rhs.id). - append(documentType, rhs.documentType). - append(title, rhs.title). - append(description, rhs.description). - append(url, rhs.url). - append(datePublished, rhs.datePublished). - append(dateModified, rhs.dateModified). - append(format, rhs.format). - append(language, rhs.language). - isEquals(); - } - - @Override - public Serializable getIdProperty() { - return id; + return new EqualsBuilder().append(datePublished, rhs.datePublished) + .append(documentType, rhs.documentType) + .append(format, rhs.format) + .append(description, rhs.description) + .append(dateModified, rhs.dateModified) + .append(language, rhs.language) + .append(id, rhs.id) + .append(title, rhs.title) + .append(url, rhs.url) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifiable.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifiable.java index fbf492b31..9916948eb 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifiable.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifiable.java @@ -1,10 +1,10 @@ package org.devgateway.ocds.persistence.mongo; -import java.io.Serializable; - import com.fasterxml.jackson.annotation.JsonIgnore; -public interface Identifiable { +import java.io.Serializable; + +public interface Identifiable extends Serializable { @JsonIgnore Serializable getIdProperty(); diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifier.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifier.java index 14a6898b9..195937bde 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifier.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifier.java @@ -1,18 +1,22 @@ package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.net.URI; + /** - * Identifier OCDS entity http://standard.open-contracting.org/latest/en/schema/reference/#identifier + * Identifier + *

*/ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "scheme", "id", @@ -22,57 +26,59 @@ public class Identifier { /** - * Organization identifiers be drawn from an existing identification scheme. - * This field is used to indicate the scheme or codelist in which the identifier will be found. - * This value should be drawn from the - * [Organization Identifier Scheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#organization-identifier-scheme). - * + * Scheme + *

+ * Organization identifiers should be drawn from an existing organization identifier list. The scheme field is + * used to indicate the list or register from which the identifier is drawn. This value should be drawn from the + * [Organization Identifier Scheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#organization-identifier-scheme) codelist. */ - @ExcelExport @JsonProperty("scheme") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("Organization identifiers should be drawn from an existing organization identifier list." + + " The scheme field is used to indicate the list or register from which the identifier is drawn. This " + + "value should be drawn from the [Organization Identifier Scheme](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#organization-identifier-scheme) codelist.") private String scheme; - /** + * ID + *

* The identifier of the organization in the selected scheme. - * */ - @ExcelExport @JsonProperty("id") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The identifier of the organization in the selected scheme.") private String id; - /** + * Legal Name + *

* The legally registered name of the organization. - * */ - @ExcelExport @JsonProperty("legalName") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The legally registered name of the organization.") private String legalName; - /** - * A URI to identify the organization, such as those provided by - * [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. - * This is not for listing the website of the organization: t - * hat can be done through the url field of the Organization contact point. - * + * URI + *

+ * A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) + * or some other relevant URI provider. This is not for listing the website of the organization: that can be done + * through the URL field of the Organization contact point. */ - @ExcelExport @JsonProperty("uri") - @Merge(MergeStrategy.ocdsVersion) - private String uri; + @ExcelExport + @JsonPropertyDescription("A URI to identify the organization, such as those provided by [Open Corporates]" + + "(http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the " + + "website of the organization: that can be done through the URL field of the Organization contact point.") + private URI uri; /** - * Organization identifiers be drawn from an existing identification scheme. - * This field is used to indicate the scheme or codelist in which the identifier will be found. - * This value should be drawn from the - * [Organization Identifier Scheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#organization-identifier-scheme). - * - * @return - * The scheme + * Scheme + *

+ * Organization identifiers should be drawn from an existing organization identifier list. The scheme field is + * used to indicate the list or register from which the identifier is drawn. This value should be drawn from the + * [Organization Identifier Scheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#organization-identifier-scheme) codelist. */ @JsonProperty("scheme") public String getScheme() { @@ -80,25 +86,22 @@ public String getScheme() { } /** - * Organization identifiers be drawn from an existing identification scheme. - * This field is used to indicate the scheme or codelist in which the identifier will be found. - * This value should be drawn from the - * [Organization Identifier Scheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#organization-identifier-scheme). - * - * @param scheme - * The scheme + * Scheme + *

+ * Organization identifiers should be drawn from an existing organization identifier list. The scheme field is + * used to indicate the list or register from which the identifier is drawn. This value should be drawn from the + * [Organization Identifier Scheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#organization-identifier-scheme) codelist. */ @JsonProperty("scheme") - public void setScheme(final String scheme) { + public void setScheme(String scheme) { this.scheme = scheme; } /** + * ID + *

* The identifier of the organization in the selected scheme. - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -106,21 +109,19 @@ public String getId() { } /** + * ID + *

* The identifier of the organization in the selected scheme. - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** + * Legal Name + *

* The legally registered name of the organization. - * - * @return - * The legalName */ @JsonProperty("legalName") public String getLegalName() { @@ -128,61 +129,60 @@ public String getLegalName() { } /** + * Legal Name + *

* The legally registered name of the organization. - * - * @param legalName - * The legalName */ @JsonProperty("legalName") - public void setLegalName(final String legalName) { + public void setLegalName(String legalName) { this.legalName = legalName; } /** - * A URI to identify the organization, such as those provided by - * [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. - * This is not for listing the website of the organization: - * that can be done through the url field of the Organization contact point. - * - * @return - * The uri + * URI + *

+ * A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) + * or some other relevant URI provider. This is not for listing the website of the organization: that can be done + * through the URL field of the Organization contact point. */ @JsonProperty("uri") - public String getUri() { + public URI getUri() { return uri; } /** - * A URI to identify the organization, such as those provided by - * [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. - * This is not for listing the website of the organization: - * that can be done through the url field of the Organization contact point. - * - * @param uri - * The uri + * URI + *

+ * A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) + * or some other relevant URI provider. This is not for listing the website of the organization: that can be done + * through the URL field of the Organization contact point. */ @JsonProperty("uri") - public void setUri(final String uri) { + public void setUri(URI uri) { this.uri = uri; } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("scheme", scheme) + .append("id", id) + .append("legalName", legalName) + .append("uri", uri) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(scheme). - append(id). - append(legalName). - append(uri). - toHashCode(); + return new HashCodeBuilder().append(legalName) + .append(id) + .append(scheme) + .append(uri) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -190,12 +190,11 @@ public boolean equals(final Object other) { return false; } Identifier rhs = ((Identifier) other); - return new EqualsBuilder(). - append(scheme, rhs.scheme). - append(id, rhs.id). - append(legalName, rhs.legalName). - append(uri, rhs.uri). - isEquals(); + return new EqualsBuilder().append(legalName, rhs.legalName) + .append(id, rhs.id) + .append(scheme, rhs.scheme) + .append(uri, rhs.uri) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Implementation.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Implementation.java index d094cf25f..d941ab935 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Implementation.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Implementation.java @@ -1,28 +1,26 @@ -package org.devgateway.ocds.persistence.mongo; -import java.util.LinkedHashSet; -import java.util.Set; +package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.LinkedHashSet; +import java.util.Set; /** * Implementation *

* Information during the performance / implementation stage of the contract. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#implementation - * */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "transactions", "milestones", @@ -31,38 +29,39 @@ public class Implementation { /** + * Transactions + *

* A list of the spending transactions made against this contract - * */ - @ExcelExport @JsonProperty("transactions") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) - private Set transactions = new LinkedHashSet<>(); - + @JsonDeserialize(as = LinkedHashSet.class) + @ExcelExport + @JsonPropertyDescription("A list of the spending transactions made against this contract") + private Set transactions = new LinkedHashSet(); /** + * Milestones + *

* As milestones are completed, milestone completions should be documented. - * */ @JsonProperty("milestones") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("As milestones are completed, milestone completions should be documented.") private Set milestones = new LinkedHashSet(); - /** + * Documents + *

* Documents and reports that are part of the implementation phase e.g. audit and evaluation reports. - * */ @JsonProperty("documents") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("Documents and reports that are part of the implementation phase e.g. audit and " + + "evaluation reports.") private Set documents = new LinkedHashSet(); /** + * Transactions + *

* A list of the spending transactions made against this contract - * - * @return - * The transactions */ @JsonProperty("transactions") public Set getTransactions() { @@ -70,21 +69,19 @@ public Set getTransactions() { } /** + * Transactions + *

* A list of the spending transactions made against this contract - * - * @param transactions - * The transactions */ @JsonProperty("transactions") - public void setTransactions(final Set transactions) { + public void setTransactions(Set transactions) { this.transactions = transactions; } /** + * Milestones + *

* As milestones are completed, milestone completions should be documented. - * - * @return - * The milestones */ @JsonProperty("milestones") public Set getMilestones() { @@ -92,21 +89,19 @@ public Set getMilestones() { } /** + * Milestones + *

* As milestones are completed, milestone completions should be documented. - * - * @param milestones - * The milestones */ @JsonProperty("milestones") - public void setMilestones(final Set milestones) { + public void setMilestones(Set milestones) { this.milestones = milestones; } /** + * Documents + *

* Documents and reports that are part of the implementation phase e.g. audit and evaluation reports. - * - * @return - * The documents */ @JsonProperty("documents") public Set getDocuments() { @@ -114,32 +109,35 @@ public Set getDocuments() { } /** + * Documents + *

* Documents and reports that are part of the implementation phase e.g. audit and evaluation reports. - * - * @param documents - * The documents */ @JsonProperty("documents") - public void setDocuments(final Set documents) { + public void setDocuments(Set documents) { this.documents = documents; } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("transactions", transactions) + .append("milestones", milestones) + .append("documents", documents) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(transactions). - append(milestones). - append(documents). - toHashCode(); + return new HashCodeBuilder() + .append(transactions) + .append(milestones) + .append(documents) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -147,11 +145,11 @@ public boolean equals(final Object other) { return false; } Implementation rhs = ((Implementation) other); - return new EqualsBuilder(). - append(transactions, rhs.transactions). - append(milestones, rhs.milestones). - append(documents, rhs.documents). - isEquals(); + return new EqualsBuilder() + .append(transactions, rhs.transactions) + .append(milestones, rhs.milestones) + .append(documents, rhs.documents) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Item.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Item.java index c58c88abc..eff4b4a92 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Item.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Item.java @@ -1,25 +1,25 @@ package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import java.io.Serializable; import java.util.LinkedHashSet; import java.util.Set; + /** + * Item + *

* A good, service, or work to be contracted. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#item - * */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "description", @@ -29,7 +29,21 @@ "unit", "deliveryLocation" }) -public class Item implements Identifiable { +public class Item { + + /** + * ID + *

+ * A local identifier to reference and merge the items by. Must be unique within a given array of items. + * (Required) + */ + @JsonProperty("id") + @JsonPropertyDescription("A local identifier to reference and merge the items by. Must be unique within a given " + + "array of items.") + @ExcelExport + private String id; + + /** * This is part of the OCDS location extension. We have decided to plug this @@ -39,65 +53,62 @@ public class Item implements Identifiable { @SuppressWarnings("rawtypes") private DefaultLocation deliveryLocation; - /** - * A local identifier to reference and merge the items by. Must be unique within a given array of items. - * (Required) - * - */ - @ExcelExport - @JsonProperty("id") - @Merge(MergeStrategy.overwrite) - private String id; /** + * Description + *

* A description of the goods, services to be provided. - * */ - @ExcelExport @JsonProperty("description") - @Merge(MergeStrategy.ocdsVersion) - private String description; - + @JsonPropertyDescription("A description of the goods, services to be provided.") @ExcelExport + private String description; + /** + * Classification + *

+ */ @JsonProperty("classification") + @ExcelExport private Classification classification; - /** - * An array of additional classifications for the item. See the - * [itemClassificationScheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) - * codelist for common options to use in OCDS. - * This may also be used to present codes from an internal classification scheme. - * + * Additional classifications + *

+ * An array of additional classifications for the item. See the [itemClassificationScheme](http://standard + * .open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) codelist for common options to + * use in OCDS. This may also be used to present codes from an internal classification scheme. */ @JsonProperty("additionalClassifications") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.ocdsVersion) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("An array of additional classifications for the item. See the [itemClassificationScheme]" + + "(http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) codelist" + + " for common options to use in OCDS. This may also be used to present codes from an internal " + + "classification scheme.") private Set additionalClassifications = new LinkedHashSet(); - /** + * Quantity + *

* The number of units required - * */ - @ExcelExport @JsonProperty("quantity") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The number of units required") private Double quantity; - /** - * Description of the unit which the good comes in e.g. hours, kilograms. - * Made up of a unit name, and the value of a single unit. - * + * Unit + *

+ * A description of the unit in which the supplies, services or works are provided (e.g. hours, kilograms) and + * the unit-price. For comparability, an established list of units can be used. */ @JsonProperty("unit") + @JsonPropertyDescription("A description of the unit in which the supplies, services or works are provided (e.g. " + + "hours, kilograms) and the unit-price. For comparability, an established list of units can be used. ") private Unit unit; /** + * ID + *

* A local identifier to reference and merge the items by. Must be unique within a given array of items. * (Required) - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -105,22 +116,20 @@ public String getId() { } /** + * ID + *

* A local identifier to reference and merge the items by. Must be unique within a given array of items. * (Required) - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** + * Description + *

* A description of the goods, services to be provided. - * - * @return - * The description */ @JsonProperty("description") public String getDescription() { @@ -128,20 +137,18 @@ public String getDescription() { } /** + * Description + *

* A description of the goods, services to be provided. - * - * @param description - * The description */ @JsonProperty("description") - public void setDescription(final String description) { + public void setDescription(String description) { this.description = description; } /** - * - * @return - * The classification + * Classification + *

*/ @JsonProperty("classification") public Classification getClassification() { @@ -149,24 +156,20 @@ public Classification getClassification() { } /** - * - * @param classification - * The classification + * Classification + *

*/ @JsonProperty("classification") - public void setClassification(final Classification classification) { + public void setClassification(Classification classification) { this.classification = classification; } /** - * An array of additional classifications for the item. See the - * [itemClassificationScheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) - * codelist for common options to use in OCDS. - * This may also be used to present codes from an internal classification scheme. - * - * @return - * The additionalClassifications + * Additional classifications + *

+ * An array of additional classifications for the item. See the [itemClassificationScheme](http://standard + * .open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) codelist for common options to + * use in OCDS. This may also be used to present codes from an internal classification scheme. */ @JsonProperty("additionalClassifications") public Set getAdditionalClassifications() { @@ -174,25 +177,21 @@ public Set getAdditionalClassifications() { } /** - * An array of additional classifications for the item. See the - * [itemClassificationScheme] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) - * codelist for common options to use in OCDS. - * This may also be used to present codes from an internal classification scheme. - * - * @param additionalClassifications - * The additionalClassifications + * Additional classifications + *

+ * An array of additional classifications for the item. See the [itemClassificationScheme](http://standard + * .open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) codelist for common options to + * use in OCDS. This may also be used to present codes from an internal classification scheme. */ @JsonProperty("additionalClassifications") - public void setAdditionalClassifications(final Set additionalClassifications) { + public void setAdditionalClassifications(Set additionalClassifications) { this.additionalClassifications = additionalClassifications; } /** + * Quantity + *

* The number of units required - * - * @return - * The quantity */ @JsonProperty("quantity") public Double getQuantity() { @@ -200,22 +199,20 @@ public Double getQuantity() { } /** + * Quantity + *

* The number of units required - * - * @param quantity - * The quantity */ @JsonProperty("quantity") - public void setQuantity(final Double quantity) { + public void setQuantity(Double quantity) { this.quantity = quantity; } /** - * Description of the unit which the good comes in e.g. hours, kilograms. - * Made up of a unit name, and the value of a single unit. - * - * @return - * The unit + * Unit + *

+ * A description of the unit in which the supplies, services or works are provided (e.g. hours, kilograms) and + * the unit-price. For comparability, an established list of units can be used. */ @JsonProperty("unit") public Unit getUnit() { @@ -223,37 +220,52 @@ public Unit getUnit() { } /** - * Description of the unit which the good comes in e.g. hours, kilograms. - * Made up of a unit name, and the value of a single unit. - * - * @param unit - * The unit + * Unit + *

+ * A description of the unit in which the supplies, services or works are provided (e.g. hours, kilograms) and + * the unit-price. For comparability, an established list of units can be used. */ @JsonProperty("unit") - public void setUnit(final Unit unit) { + public void setUnit(Unit unit) { this.unit = unit; } + + public DefaultLocation getDeliveryLocation() { + return deliveryLocation; + } + + public void setDeliveryLocation(DefaultLocation deliveryLocation) { + this.deliveryLocation = deliveryLocation; + } + + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("id", id) + .append("description", description) + .append("classification", classification) + .append("additionalClassifications", additionalClassifications) + .append("quantity", quantity) + .append("unit", unit) + .append("deliveryLocation", deliveryLocation) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(id). - append(description). - append(classification). - append(additionalClassifications). - append(quantity). - append(unit). - append(deliveryLocation). - toHashCode(); + return new HashCodeBuilder().append(additionalClassifications) + .append(unit) + .append(quantity) + .append(description) + .append(id) + .append(classification) + .append(deliveryLocation) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -261,27 +273,14 @@ public boolean equals(final Object other) { return false; } Item rhs = ((Item) other); - return new EqualsBuilder(). - append(id, rhs.id). - append(description, rhs.description). - append(classification, rhs.classification). - append(additionalClassifications, rhs.additionalClassifications). - append(quantity, rhs.quantity). - append(unit, rhs.unit). - append(deliveryLocation, rhs.deliveryLocation). - isEquals(); - } - - public DefaultLocation getDeliveryLocation() { - return deliveryLocation; + return new EqualsBuilder().append(additionalClassifications, rhs.additionalClassifications) + .append(unit, rhs.unit) + .append(quantity, rhs.quantity) + .append(description, rhs.description) + .append(id, rhs.id) + .append(classification, rhs.classification) + .append(deliveryLocation, rhs.deliveryLocation) + .isEquals(); } - public void setDeliveryLocation(final DefaultLocation deliveryLocation) { - this.deliveryLocation = deliveryLocation; - } - - @Override - public Serializable getIdProperty() { - return id; - } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Milestone.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Milestone.java index 8be7e8239..dccbccd8f 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Milestone.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Milestone.java @@ -1,17 +1,16 @@ package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; @@ -20,87 +19,129 @@ /** - * Milestone OCDS entity http://standard.open-contracting.org/latest/en/schema/reference/#milestone + * Milestone + *

*/ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "title", + "type", "description", + "code", "dueDate", + "dateMet", "dateModified", "status", "documents" }) -public class Milestone implements Identifiable { +public class Milestone { /** - * A local identifier for this milestone, unique within this block. This field is used to keep track of - * multiple revisions of a milestone through the compilation from release to record mechanism. + * ID + *

+ * A local identifier for this milestone, unique within this block. This field is used to keep track of multiple + * revisions of a milestone through the compilation from release to record mechanism. * (Required) - * */ @JsonProperty("id") - @Merge(MergeStrategy.overwrite) + @JsonPropertyDescription("A local identifier for this milestone, unique within this block. This field is used to " + + "keep track of multiple revisions of a milestone through the compilation from release to record " + + "mechanism.") private String id; - /** + * Title + *

* Milestone title - * */ @JsonProperty("title") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Milestone title") private String title; - /** + * Milestone type + *

+ * The type of milestone, drawn from an extended [milestoneType codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#milestone-type). + */ + @JsonProperty("type") + @JsonPropertyDescription("The type of milestone, drawn from an extended [milestoneType codelist](http://standard" + + ".open-contracting.org/latest/en/schema/codelists/#milestone-type).") + private String type; + /** + * Description + *

* A description of the milestone. - * */ @JsonProperty("description") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("A description of the milestone.") private String description; - /** + * Milestone code + *

+ * Milestone codes can be used to track specific events that take place for a particular kind of contracting + * process. For example, a code of 'approvalLetter' could be used to allow applications to understand this + * milestone represents the date an approvalLetter is due or signed. Milestone codes is an open codelist, and + * codes should be agreed among data producers and the applications using that data. + */ + @JsonProperty("code") + @JsonPropertyDescription("Milestone codes can be used to track specific events that take place for a particular " + + "kind of contracting process. For example, a code of 'approvalLetter' could be used to allow " + + "applications to understand this milestone represents the date an approvalLetter is due or signed. " + + "Milestone codes is an open codelist, and codes should be agreed among data producers and the " + + "applications using that data.") + private String code; + /** + * Due date + *

* The date the milestone is due. - * */ @JsonProperty("dueDate") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The date the milestone is due.") private Date dueDate; - /** + * Date met + *

+ * The date on which the milestone was met. + */ + @JsonProperty("dateMet") + @JsonPropertyDescription("The date on which the milestone was met.") + private Date dateMet; + /** + * Date modified + *

* The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct. - * */ @JsonProperty("dateModified") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The date the milestone was last reviewed or modified and the status was altered or " + + "confirmed to still be correct.") private Date dateModified; - /** - * The status that was realized on the date provided in dateModified, drawn from the - * [milestoneStatus codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#milestone-status). - * + * Status + *

+ * The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#milestone-status). */ @JsonProperty("status") - @Merge(MergeStrategy.ocdsVersion) - private Milestone.Status status; - + @JsonPropertyDescription("The status that was realized on the date provided in dateModified, drawn from the " + + "[milestoneStatus codelist](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#milestone-status).") + private Status status; /** - * List of documents associated with this milestone. - * + * Documents + *

+ * List of documents associated with this milestone (Deprecated in 1.1). */ @JsonProperty("documents") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("List of documents associated with this milestone (Deprecated in 1.1).") private Set documents = new LinkedHashSet(); /** - * A local identifier for this milestone, unique within this block. This field is used to keep track of - * multiple revisions of a milestone through the compilation from release to record mechanism. + * ID + *

+ * A local identifier for this milestone, unique within this block. This field is used to keep track of multiple + * revisions of a milestone through the compilation from release to record mechanism. * (Required) - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -108,23 +149,21 @@ public String getId() { } /** - * A local identifier for this milestone, unique within this block. This field is used to keep track of - * multiple revisions of a milestone through the compilation from release to record mechanism. + * ID + *

+ * A local identifier for this milestone, unique within this block. This field is used to keep track of multiple + * revisions of a milestone through the compilation from release to record mechanism. * (Required) - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** + * Title + *

* Milestone title - * - * @return - * The title */ @JsonProperty("title") public String getTitle() { @@ -132,21 +171,41 @@ public String getTitle() { } /** + * Title + *

* Milestone title - * - * @param title - * The title */ @JsonProperty("title") - public void setTitle(final String title) { + public void setTitle(String title) { this.title = title; } /** + * Milestone type + *

+ * The type of milestone, drawn from an extended [milestoneType codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#milestone-type). + */ + @JsonProperty("type") + public String getType() { + return type; + } + + /** + * Milestone type + *

+ * The type of milestone, drawn from an extended [milestoneType codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#milestone-type). + */ + @JsonProperty("type") + public void setType(String type) { + this.type = type; + } + + /** + * Description + *

* A description of the milestone. - * - * @return - * The description */ @JsonProperty("description") public String getDescription() { @@ -154,21 +213,45 @@ public String getDescription() { } /** + * Description + *

* A description of the milestone. - * - * @param description - * The description */ @JsonProperty("description") - public void setDescription(final String description) { + public void setDescription(String description) { this.description = description; } /** + * Milestone code + *

+ * Milestone codes can be used to track specific events that take place for a particular kind of contracting + * process. For example, a code of 'approvalLetter' could be used to allow applications to understand this + * milestone represents the date an approvalLetter is due or signed. Milestone codes is an open codelist, and + * codes should be agreed among data producers and the applications using that data. + */ + @JsonProperty("code") + public String getCode() { + return code; + } + + /** + * Milestone code + *

+ * Milestone codes can be used to track specific events that take place for a particular kind of contracting + * process. For example, a code of 'approvalLetter' could be used to allow applications to understand this + * milestone represents the date an approvalLetter is due or signed. Milestone codes is an open codelist, and + * codes should be agreed among data producers and the applications using that data. + */ + @JsonProperty("code") + public void setCode(String code) { + this.code = code; + } + + /** + * Due date + *

* The date the milestone is due. - * - * @return - * The dueDate */ @JsonProperty("dueDate") public Date getDueDate() { @@ -176,21 +259,39 @@ public Date getDueDate() { } /** + * Due date + *

* The date the milestone is due. - * - * @param dueDate - * The dueDate */ @JsonProperty("dueDate") - public void setDueDate(final Date dueDate) { + public void setDueDate(Date dueDate) { this.dueDate = dueDate; } /** + * Date met + *

+ * The date on which the milestone was met. + */ + @JsonProperty("dateMet") + public Date getDateMet() { + return dateMet; + } + + /** + * Date met + *

+ * The date on which the milestone was met. + */ + @JsonProperty("dateMet") + public void setDateMet(Date dateMet) { + this.dateMet = dateMet; + } + + /** + * Date modified + *

* The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct. - * - * @return - * The dateModified */ @JsonProperty("dateModified") public Date getDateModified() { @@ -198,47 +299,41 @@ public Date getDateModified() { } /** + * Date modified + *

* The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct. - * - * @param dateModified - * The dateModified */ @JsonProperty("dateModified") - public void setDateModified(final Date dateModified) { + public void setDateModified(Date dateModified) { this.dateModified = dateModified; } /** - * The status that was realized on the date provided in dateModified, drawn from the - * [milestoneStatus codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#milestone-status). - * - * @return - * The status + * Status + *

+ * The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#milestone-status). */ @JsonProperty("status") - public Milestone.Status getStatus() { + public Status getStatus() { return status; } /** - * The status that was realized on the date provided in dateModified, drawn from the - * [milestoneStatus codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#milestone-status). - * - * @param status - * The status + * Status + *

+ * The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#milestone-status). */ @JsonProperty("status") - public void setStatus(final Milestone.Status status) { + public void setStatus(Status status) { this.status = status; } /** - * List of documents associated with this milestone. - * - * @return - * The documents + * Documents + *

+ * List of documents associated with this milestone (Deprecated in 1.1). */ @JsonProperty("documents") public Set getDocuments() { @@ -246,36 +341,47 @@ public Set getDocuments() { } /** - * List of documents associated with this milestone. - * - * @param documents - * The documents + * Documents + *

+ * List of documents associated with this milestone (Deprecated in 1.1). */ @JsonProperty("documents") - public void setDocuments(final Set documents) { + public void setDocuments(Set documents) { this.documents = documents; } @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("id", id) + .append("title", title) + .append("type", type) + .append("description", description) + .append("code", code) + .append("dueDate", dueDate) + .append("dateMet", dateMet) + .append("dateModified", dateModified) + .append("status", status) + .append("documents", documents) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(id). - append(title). - append(description). - append(dueDate). - append(dateModified). - append(status). - append(documents). - toHashCode(); + return new HashCodeBuilder().append(code) + .append(documents) + .append(dueDate) + .append(dateMet) + .append(description) + .append(dateModified) + .append(id) + .append(title) + .append(type) + .append(status) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -283,47 +389,51 @@ public boolean equals(final Object other) { return false; } Milestone rhs = ((Milestone) other); - return new EqualsBuilder(). - append(id, rhs.id). - append(title, rhs.title). - append(description, rhs.description). - append(dueDate, rhs.dueDate). - append(dateModified, rhs.dateModified). - append(status, rhs.status). - append(documents, rhs.documents). - isEquals(); + return new EqualsBuilder().append(code, rhs.code) + .append(documents, rhs.documents) + .append(dueDate, rhs.dueDate) + .append(dateMet, rhs.dateMet) + .append(description, rhs.description) + .append(dateModified, rhs.dateModified) + .append(id, rhs.id) + .append(title, rhs.title) + .append(type, rhs.type) + .append(status, rhs.status) + .isEquals(); } public enum Status { - met("met"), - - notMet("notMet"), - - partiallyMet("partiallyMet"); + SCHEDULED("scheduled"), + MET("met"), + NOT_MET("notMet"), + PARTIALLY_MET("partiallyMet"); private final String value; - - private static final Map CONSTANTS = new HashMap(); + private static final Map CONSTANTS = new HashMap(); static { - for (Milestone.Status c: values()) { + for (Status c : values()) { CONSTANTS.put(c.value, c); } } - Status(final String value) { + Status(String value) { this.value = value; } - @JsonValue @Override public String toString() { return this.value; } + @JsonValue + public String value() { + return this.value; + } + @JsonCreator - public static Milestone.Status fromValue(final String value) { - Milestone.Status constant = CONSTANTS.get(value); + public static Status fromValue(String value) { + Status constant = CONSTANTS.get(value); if (constant == null) { throw new IllegalArgumentException(value); } else { @@ -333,8 +443,4 @@ public static Milestone.Status fromValue(final String value) { } - @Override - public Serializable getIdProperty() { - return id; - } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Organization.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Organization.java index efc588247..003a09556 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Organization.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Organization.java @@ -1,133 +1,212 @@ package org.devgateway.ocds.persistence.mongo; -import java.io.Serializable; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; - import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Organization *

- * An organization. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#organization - * + * A party (organization) */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ + "name", + "id", "identifier", "additionalIdentifiers", - "name", "address", "contactPoint", - "roles" + "roles", + "details" }) -@Document public class Organization implements Identifiable { - @ExcelExport - @Id - private String id; - - @JsonProperty("roles") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - private Set roles = new LinkedHashSet(); + /** + * Common name + *

+ * A common name for this organization or other participant in the contracting process. The identifier object + * provides an space for the formal legal name, and so this may either repeat that value, or could provide the + * common name by which this organization or entity is known. This field may also include details of the + * department or sub-unit involved in this contracting process. + */ + @JsonProperty("name") @ExcelExport + @JsonPropertyDescription("A common name for this organization or other participant in the contracting process. " + + "The identifier object provides an space for the formal legal name, and so this may either repeat that " + + "value, or could provide the common name by which this organization or entity is known. This field may " + + "also include details of the department or sub-unit involved in this contracting process.") + private String name; + /** + * Entity ID + *

+ * The ID used for cross-referencing to this party from other sections of the release. This field may be built + * with the following structure {identifier.scheme}-{identifier.id}(-{department-identifier}). + */ + @JsonProperty("id") + @ExcelExport + @JsonPropertyDescription("The ID used for cross-referencing to this party from other sections of the release. " + + "This field may be built with the following structure {identifier.scheme}-{identifier.id}" + + "(-{department-identifier}).") + private String id; + /** + * Identifier + *

+ */ @JsonProperty("identifier") + @ExcelExport private Identifier identifier; /** - * A list of additional / supplemental identifiers for the organization, using the - * [organization identifier guidance] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers). - * This could be used to provide an internally used identifier for - * this organization in addition to the primary legal entity identifier. - * + * Additional identifiers + *

+ * A list of additional / supplemental identifiers for the organization or participant, using the [organization + * identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/). This could be used + * to provide an internally used identifier for this organization in addition to the primary legal entity + * identifier. */ @JsonProperty("additionalIdentifiers") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.ocdsVersion) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("A list of additional / supplemental identifiers for the organization or participant, " + + "using the [organization identifier guidance](http://standard.open-contracting" + + ".org/latest/en/schema/identifiers/). This could be used to provide an internally used identifier for " + + "this organization in addition to the primary legal entity identifier.") private Set additionalIdentifiers = new LinkedHashSet(); - - /** - * The common name of the organization. The ID property provides an space for the formal legal name, - * and so this may either repeat that value, or could provide the common name by which this organization is known. - * This field could also include details of the department or sub-unit involved in this contracting process. - * - */ - @ExcelExport - @JsonProperty("name") - private String name; - /** + * Address + *

* An address. This may be the legally registered address of the organization, or may be a correspondence address * for this particular contracting process. - * */ - @ExcelExport @JsonProperty("address") + @JsonPropertyDescription("An address. This may be the legally registered address of the organization, or may be a" + + " correspondence address for this particular contracting process.") + @ExcelExport private Address address; - /** + * Contact point + *

* An person, contact point or department to contact in relation to this contracting process. - * */ - @ExcelExport @JsonProperty("contactPoint") + @JsonPropertyDescription("An person, contact point or department to contact in relation to this contracting " + + "process.") + @ExcelExport private ContactPoint contactPoint; + /** + * Party roles + *

+ * The party's role(s) in the contracting process. Role(s) should be taken from the [partyRole codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#party-role). Values from the provided + * codelist should be used wherever possible, though extended values can be provided if the codelist does not + * have a relevant code. + */ + @JsonProperty("roles") + @JsonPropertyDescription("The party's role(s) in the contracting process. Role(s) should be taken from the " + + "[partyRole codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#party-role). " + + "Values from the provided codelist should be used wherever possible, though extended values can be " + + "provided if the codelist does not have a relevant code.") + private List roles = new ArrayList(); + /** + * Details + *

+ * Additional classification information about parties can be provided using partyDetail extensions that define + * particular properties and classification schemes. + */ + @JsonProperty("details") + @JsonPropertyDescription("Additional classification information about parties can be provided using partyDetail " + + "extensions that define particular properties and classification schemes. ") + private Details details; + /** + * Common name + *

+ * A common name for this organization or other participant in the contracting process. The identifier object + * provides an space for the formal legal name, and so this may either repeat that value, or could provide the + * common name by which this organization or entity is known. This field may also include details of the + * department or sub-unit involved in this contracting process. + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * Common name + *

+ * A common name for this organization or other participant in the contracting process. The identifier object + * provides an space for the formal legal name, and so this may either repeat that value, or could provide the + * common name by which this organization or entity is known. This field may also include details of the + * department or sub-unit involved in this contracting process. + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + /** + * Entity ID + *

+ * The ID used for cross-referencing to this party from other sections of the release. This field may be built + * with the following structure {identifier.scheme}-{identifier.id}(-{department-identifier}). + */ + @JsonProperty("id") public String getId() { return id; } /** - * - * @return - * The identifier + * Entity ID + *

+ * The ID used for cross-referencing to this party from other sections of the release. This field may be built + * with the following structure {identifier.scheme}-{identifier.id}(-{department-identifier}). + */ + @JsonProperty("id") + public void setId(String id) { + this.id = id; + } + + /** + * Identifier + *

*/ @JsonProperty("identifier") public Identifier getIdentifier() { return identifier; } - public void setId(final String id) { - this.id = id; - } - /** - * - * @param identifier - * The identifier + * Identifier + *

*/ @JsonProperty("identifier") - public void setIdentifier(final Identifier identifier) { + public void setIdentifier(Identifier identifier) { this.identifier = identifier; } /** - * A list of additional / supplemental identifiers for the organization, using the - * [organization identifier guidance] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers). - * This could be used to provide an internally used identifier for - * this organization in addition to the primary legal entity identifier. - * - * @return - * The additionalIdentifiers + * Additional identifiers + *

+ * A list of additional / supplemental identifiers for the organization or participant, using the [organization + * identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/). This could be used + * to provide an internally used identifier for this organization in addition to the primary legal entity + * identifier. */ @JsonProperty("additionalIdentifiers") public Set getAdditionalIdentifiers() { @@ -135,52 +214,23 @@ public Set getAdditionalIdentifiers() { } /** - * A list of additional / supplemental identifiers for the organization, using the - * [organization identifier guidance] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers). - * This could be used to provide an internally used identifier for - * this organization in addition to the primary legal entity identifier. - * - * @param additionalIdentifiers - * The additionalIdentifiers + * Additional identifiers + *

+ * A list of additional / supplemental identifiers for the organization or participant, using the [organization + * identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/). This could be used + * to provide an internally used identifier for this organization in addition to the primary legal entity + * identifier. */ @JsonProperty("additionalIdentifiers") - public void setAdditionalIdentifiers(final Set additionalIdentifiers) { + public void setAdditionalIdentifiers(Set additionalIdentifiers) { this.additionalIdentifiers = additionalIdentifiers; } /** - * The common name of the organization. The ID property provides an space for the formal legal name, - * and so this may either repeat that value, or could provide the common name by which this organization is known. - * This field could also include details of the department or sub-unit involved in this contracting process. - * - * @return - * The name - */ - @JsonProperty("name") - public String getName() { - return name; - } - - /** - * The common name of the organization. The ID property provides an space for the formal legal name, - * and so this may either repeat that value, or could provide the common name by which this organization is known. - * This field could also include details of the department or sub-unit involved in this contracting process. - * - * @param name - * The name - */ - @JsonProperty("name") - public void setName(final String name) { - this.name = name; - } - - /** - * An address. This may be the legally registered address of the organization, - * or may be a correspondence address for this particular contracting process. - * - * @return - * The address + * Address + *

+ * An address. This may be the legally registered address of the organization, or may be a correspondence address + * for this particular contracting process. */ @JsonProperty("address") public Address getAddress() { @@ -188,57 +238,112 @@ public Address getAddress() { } /** - * An address. This may be the legally registered address of the organization, - * or may be a correspondence address for this particular contracting process. - * - * @param address - * The address + * Address + *

+ * An address. This may be the legally registered address of the organization, or may be a correspondence address + * for this particular contracting process. */ @JsonProperty("address") - public void setAddress(final Address address) { + public void setAddress(Address address) { this.address = address; } /** + * Contact point + *

* An person, contact point or department to contact in relation to this contracting process. - * - * @return - * The contactPoint */ @JsonProperty("contactPoint") public ContactPoint getContactPoint() { return contactPoint; } - @Override - public String toString() { - return ToStringBuilder.reflectionToString(this); + /** + * Contact point + *

+ * An person, contact point or department to contact in relation to this contracting process. + */ + @JsonProperty("contactPoint") + public void setContactPoint(ContactPoint contactPoint) { + this.contactPoint = contactPoint; } - @Override - public int hashCode() { - return new HashCodeBuilder(). - append(identifier). - append(additionalIdentifiers). - append(name). - append(address). - append(contactPoint). - toHashCode(); + /** + * Party roles + *

+ * The party's role(s) in the contracting process. Role(s) should be taken from the [partyRole codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#party-role). Values from the provided + * codelist should be used wherever possible, though extended values can be provided if the codelist does not + * have a relevant code. + */ + @JsonProperty("roles") + public List getRoles() { + return roles; } /** - * An person, contact point or department to contact in relation to this contracting process. - * - * @param contactPoint - * The contactPoint + * Party roles + *

+ * The party's role(s) in the contracting process. Role(s) should be taken from the [partyRole codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#party-role). Values from the provided + * codelist should be used wherever possible, though extended values can be provided if the codelist does not + * have a relevant code. */ - @JsonProperty("contactPoint") - public void setContactPoint(final ContactPoint contactPoint) { - this.contactPoint = contactPoint; + @JsonProperty("roles") + public void setRoles(List roles) { + this.roles = roles; + } + + /** + * Details + *

+ * Additional classification information about parties can be provided using partyDetail extensions that define + * particular properties and classification schemes. + */ + @JsonProperty("details") + public Details getDetails() { + return details; + } + + /** + * Details + *

+ * Additional classification information about parties can be provided using partyDetail extensions that define + * particular properties and classification schemes. + */ + @JsonProperty("details") + public void setDetails(Details details) { + this.details = details; } @Override - public boolean equals(final Object other) { + public String toString() { + return new ToStringBuilder(this).append("name", name) + .append("id", id) + .append("identifier", identifier) + .append("additionalIdentifiers", additionalIdentifiers) + .append("address", address) + .append("contactPoint", contactPoint) + .append("roles", roles) + .append("details", details) + .toString(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(identifier) + .append(address) + .append(contactPoint) + .append(roles) + .append(name) + .append(additionalIdentifiers) + .append(details) + .append(id) + .toHashCode(); + } + + @Override + public boolean equals(Object other) { if (other == this) { return true; } @@ -246,16 +351,24 @@ public boolean equals(final Object other) { return false; } Organization rhs = ((Organization) other); - return new EqualsBuilder(). - append(identifier, rhs.identifier). - append(additionalIdentifiers, rhs.additionalIdentifiers). - append(name, rhs.name). - append(address, rhs.address). - append(contactPoint, rhs.contactPoint). - isEquals(); + return new EqualsBuilder().append(identifier, rhs.identifier) + .append(address, rhs.address) + .append(contactPoint, rhs.contactPoint) + .append(roles, rhs.roles) + .append(name, rhs.name) + .append(additionalIdentifiers, rhs.additionalIdentifiers) + .append(details, rhs.details) + .append(id, rhs.id) + .isEquals(); } + @Override + public Serializable getIdProperty() { + return id; + } + + @Deprecated public enum OrganizationType { procuringEntity("procuringEntity"), @@ -296,20 +409,4 @@ public static OrganizationType fromValue(final String value) { } - public Set getRoles() { - return roles; - } - - public void setRoles(final Set roles) { - this.roles = roles; - } - - @Override - public Serializable getIdProperty() { - return id; - } - - - } - diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Period.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Period.java index 8fd2aed23..e5976b250 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Period.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Period.java @@ -1,52 +1,77 @@ package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; import java.util.Date; + /** * Period *

- * - * http://standard.open-contracting.org/latest/en/schema/reference/#period - * */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "startDate", - "endDate" + "endDate", + "maxExtentDate", + "durationInDays" }) public class Period { /** - * The start date for the period. - * + * Start date + *

+ * The start date for the period. When known, a precise start date must always be provided. */ - @ExcelExport @JsonProperty("startDate") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The start date for the period. When known, a precise start date must always be provided.") private Date startDate; - /** - * The end date for the period. - * + * End date + *

+ * The end date for the period. When known, a precise end date must always be provided. */ - @ExcelExport @JsonProperty("endDate") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The end date for the period. When known, a precise end date must always be provided.") private Date endDate; + /** + * Maximum extent + *

+ * The period cannot be extended beyond this date. This field is optional, and can be used to express the maximum + * available data for extension or renewal of this period. + */ + @JsonProperty("maxExtentDate") + @JsonPropertyDescription("The period cannot be extended beyond this date. This field is optional, and can be used" + + " to express the maximum available data for extension or renewal of this period.") + private Date maxExtentDate; + /** + * Duration (days) + *

+ * The maximum duration of this period in days. A user interface may wish to collect or display this data in + * months or years as appropriate, but should convert it into days when completing this field. This field can be + * used when exact dates are not known. Where a startDate and endDate are given, this field is optional, and + * should reflect the difference between those two days. Where a startDate and maxExtentDate are given, this + * field is optional, and should reflect the difference between startDate and maxExtentDate. + */ + @JsonProperty("durationInDays") + @JsonPropertyDescription("The maximum duration of this period in days. A user interface may wish to collect or " + + "display this data in months or years as appropriate, but should convert it into days when completing " + + "this field. This field can be used when exact dates are not known. Where a startDate and endDate are " + + "given, this field is optional, and should reflect the difference between those two days. Where a " + + "startDate and maxExtentDate are given, this field is optional, and should reflect the difference " + + "between startDate and maxExtentDate.") + private Integer durationInDays; /** - * The start date for the period. - * - * @return - * The startDate + * Start date + *

+ * The start date for the period. When known, a precise start date must always be provided. */ @JsonProperty("startDate") public Date getStartDate() { @@ -54,21 +79,19 @@ public Date getStartDate() { } /** - * The start date for the period. - * - * @param startDate - * The startDate + * Start date + *

+ * The start date for the period. When known, a precise start date must always be provided. */ @JsonProperty("startDate") - public void setStartDate(final Date startDate) { + public void setStartDate(Date startDate) { this.startDate = startDate; } /** - * The end date for the period. - * - * @return - * The endDate + * End date + *

+ * The end date for the period. When known, a precise end date must always be provided. */ @JsonProperty("endDate") public Date getEndDate() { @@ -76,31 +99,86 @@ public Date getEndDate() { } /** - * The end date for the period. - * - * @param endDate - * The endDate + * End date + *

+ * The end date for the period. When known, a precise end date must always be provided. */ @JsonProperty("endDate") - public void setEndDate(final Date endDate) { + public void setEndDate(Date endDate) { this.endDate = endDate; } + /** + * Maximum extent + *

+ * The period cannot be extended beyond this date. This field is optional, and can be used to express the maximum + * available data for extension or renewal of this period. + */ + @JsonProperty("maxExtentDate") + public Date getMaxExtentDate() { + return maxExtentDate; + } + + /** + * Maximum extent + *

+ * The period cannot be extended beyond this date. This field is optional, and can be used to express the maximum + * available data for extension or renewal of this period. + */ + @JsonProperty("maxExtentDate") + public void setMaxExtentDate(Date maxExtentDate) { + this.maxExtentDate = maxExtentDate; + } + + /** + * Duration (days) + *

+ * The maximum duration of this period in days. A user interface may wish to collect or display this data in + * months or years as appropriate, but should convert it into days when completing this field. This field can be + * used when exact dates are not known. Where a startDate and endDate are given, this field is optional, and + * should reflect the difference between those two days. Where a startDate and maxExtentDate are given, this + * field is optional, and should reflect the difference between startDate and maxExtentDate. + */ + @JsonProperty("durationInDays") + public Integer getDurationInDays() { + return durationInDays; + } + + /** + * Duration (days) + *

+ * The maximum duration of this period in days. A user interface may wish to collect or display this data in + * months or years as appropriate, but should convert it into days when completing this field. This field can be + * used when exact dates are not known. Where a startDate and endDate are given, this field is optional, and + * should reflect the difference between those two days. Where a startDate and maxExtentDate are given, this + * field is optional, and should reflect the difference between startDate and maxExtentDate. + */ + @JsonProperty("durationInDays") + public void setDurationInDays(Integer durationInDays) { + this.durationInDays = durationInDays; + } + + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("startDate", startDate) + .append("endDate", endDate) + .append("maxExtentDate", maxExtentDate) + .append("durationInDays", durationInDays) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(startDate). - append(endDate). - toHashCode(); + return new HashCodeBuilder().append(durationInDays) + .append(endDate) + .append(startDate) + .append(maxExtentDate) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -108,10 +186,11 @@ public boolean equals(final Object other) { return false; } Period rhs = ((Period) other); - return new EqualsBuilder(). - append(startDate, rhs.startDate). - append(endDate, rhs.endDate). - isEquals(); + return new EqualsBuilder().append(durationInDays, rhs.durationInDays) + .append(endDate, rhs.endDate) + .append(startDate, rhs.startDate) + .append(maxExtentDate, rhs.maxExtentDate) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Planning.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Planning.java index 31eeb32be..90af61419 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Planning.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Planning.java @@ -1,109 +1,84 @@ package org.devgateway.ocds.persistence.mongo; -import java.util.ArrayList; -import java.util.List; - +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.ArrayList; +import java.util.List; /** * Planning *

* Information from the planning phase of the contracting process. Note that many other fields may be filled in a - * planning release, in the appropriate fields in other schema sections, - * these would likely be estimates at this stage e.g. totalValue in tender - * - * http://standard.open-contracting.org/latest/en/schema/reference/#planning - * + * planning release, in the appropriate fields in other schema sections, these would likely be estimates at this + * stage e.g. totalValue in tender */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ - "budget", "rationale", - "documents" + "budget", + "documents", + "milestones" }) public class Planning { /** - * Budget Information + * Rationale *

- * This section contain information about the budget line, and associated projects, - * through which this contracting process is funded. It draws upon data model of the - * [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md), - * and should be used to cross-reference to more detailed information held using a Budget Data Package, or, - * where no linked Budget Data Package is available, to provide enough information to allow a user to manually or - * automatically cross-reference with another published source of budget and project information. - * + * The rationale for the procurement provided in free text. More detail can be provided in an attached document. */ + @JsonProperty("rationale") + @JsonPropertyDescription("The rationale for the procurement provided in free text. More detail can be provided in" + + " an attached document.") @ExcelExport - @JsonProperty("budget") - private Budget budget; - + private String rationale; /** - * The rationale for the procurement provided in free text. More detail can be provided in an attached document. - * + * Budget information + *

+ * This section contain information about the budget line, and associated projects, through which this + * contracting process is funded. It draws upon data model of the [Fiscal Data Package](http://fiscal + * .dataprotocols.org/), and should be used to cross-reference to more detailed information held using a Budget + * Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a + * user to manually or automatically cross-reference with another published source of budget and project + * information. */ + @JsonProperty("budget") + @JsonPropertyDescription("This section contain information about the budget line, and associated projects, " + + "through which this contracting process is funded. It draws upon data model of the [Fiscal Data " + + "Package](http://fiscal.dataprotocols.org/), and should be used to cross-reference to more detailed " + + "information held using a Budget Data Package, or, where no linked Budget Data Package is available, to" + + " provide enough information to allow a user to manually or automatically cross-reference with another " + + "published source of budget and project information.") @ExcelExport - @JsonProperty("rationale") - @Merge(MergeStrategy.ocdsVersion) - private String rationale; - + private Budget budget; /** + * Documents + *

* A list of documents related to the planning process. - * */ @JsonProperty("documents") - @Merge(MergeStrategy.arrayMergeById) - private List documents = new ArrayList<>(); - + @JsonPropertyDescription("A list of documents related to the planning process.") + private List documents = new ArrayList(); /** - * Budget Information + * Planning milestones *

- * This section contain information about the budget line, and associated projects, - * through which this contracting process is funded. It draws upon data model of the - * [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md), - * and should be used to cross-reference to more detailed information held using a Budget Data Package, or, - * where no linked Budget Data Package is available, to provide enough information to allow a user to manually or - * automatically cross-reference with another published source of budget and project information. - * - * @return - * The budget + * A list of milestones associated with the planning stage. */ - @JsonProperty("budget") - public Budget getBudget() { - return budget; - } + @JsonProperty("milestones") + @JsonPropertyDescription("A list of milestones associated with the planning stage.") + private List milestones = new ArrayList(); /** - * Budget Information + * Rationale *

- * This section contain information about the budget line, and associated projects, - * through which this contracting process is funded. It draws upon data model of the - * [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md), - * and should be used to cross-reference to more detailed information held using a Budget Data Package, or, - * where no linked Budget Data Package is available, to provide enough information to allow a user to manually or - * automatically cross-reference with another published source of budget and project information. - * - * @param budget - * The budget - */ - @JsonProperty("budget") - public void setBudget(final Budget budget) { - this.budget = budget; - } - - /** * The rationale for the procurement provided in free text. More detail can be provided in an attached document. - * - * @return - * The rationale */ @JsonProperty("rationale") public String getRationale() { @@ -111,21 +86,49 @@ public String getRationale() { } /** + * Rationale + *

* The rationale for the procurement provided in free text. More detail can be provided in an attached document. - * - * @param rationale - * The rationale */ @JsonProperty("rationale") - public void setRationale(final String rationale) { + public void setRationale(String rationale) { this.rationale = rationale; } /** + * Budget information + *

+ * This section contain information about the budget line, and associated projects, through which this + * contracting process is funded. It draws upon data model of the [Fiscal Data Package](http://fiscal + * .dataprotocols.org/), and should be used to cross-reference to more detailed information held using a Budget + * Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a + * user to manually or automatically cross-reference with another published source of budget and project + * information. + */ + @JsonProperty("budget") + public Budget getBudget() { + return budget; + } + + /** + * Budget information + *

+ * This section contain information about the budget line, and associated projects, through which this + * contracting process is funded. It draws upon data model of the [Fiscal Data Package](http://fiscal + * .dataprotocols.org/), and should be used to cross-reference to more detailed information held using a Budget + * Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a + * user to manually or automatically cross-reference with another published source of budget and project + * information. + */ + @JsonProperty("budget") + public void setBudget(Budget budget) { + this.budget = budget; + } + + /** + * Documents + *

* A list of documents related to the planning process. - * - * @return - * The documents */ @JsonProperty("documents") public List getDocuments() { @@ -133,32 +136,56 @@ public List getDocuments() { } /** + * Documents + *

* A list of documents related to the planning process. - * - * @param documents - * The documents */ @JsonProperty("documents") - public void setDocuments(final List documents) { + public void setDocuments(List documents) { this.documents = documents; } + /** + * Planning milestones + *

+ * A list of milestones associated with the planning stage. + */ + @JsonProperty("milestones") + public List getMilestones() { + return milestones; + } + + /** + * Planning milestones + *

+ * A list of milestones associated with the planning stage. + */ + @JsonProperty("milestones") + public void setMilestones(List milestones) { + this.milestones = milestones; + } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("rationale", rationale) + .append("budget", budget) + .append("documents", documents) + .append("milestones", milestones) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(budget). - append(rationale). - append(documents). - toHashCode(); + return new HashCodeBuilder() + .append(milestones) + .append(rationale) + .append(documents) + .append(budget) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -166,11 +193,12 @@ public boolean equals(final Object other) { return false; } Planning rhs = ((Planning) other); - return new EqualsBuilder(). - append(budget, rhs.budget). - append(rationale, rhs.rationale). - append(documents, rhs.documents). - isEquals(); + return new EqualsBuilder() + .append(milestones, rhs.milestones) + .append(rationale, rhs.rationale) + .append(documents, rhs.documents) + .append(budget, rhs.budget) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Record.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Record.java index b50b21961..f7063fd37 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Record.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Record.java @@ -1,80 +1,69 @@ - package org.devgateway.ocds.persistence.mongo; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; -import org.springframework.data.mongodb.core.mapping.Document; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * Record + *

+ */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "ocid", "releases", - "compiledRelease" + "compiledRelease", + "versionedRelease" }) -@Document -public class Record implements Identifiable { +public class Record { /** * Open Contracting ID *

- * A unique identifier that identifies the unique Open Contracting Process. - * For more information see: - * http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/ - * definitions/#contracting-process (Required) - * + * A unique identifier that identifies the unique Open Contracting Process. For more information see: + * http://standard.open-contracting.org/latest/en/getting_started/contracting_process/ + * (Required) */ @JsonProperty("ocid") - @Id + @JsonPropertyDescription("A unique identifier that identifies the unique Open Contracting Process. For more " + + "information see: http://standard.open-contracting.org/latest/en/getting_started/contracting_process/") private String ocid; /** - * Linked releases + * Releases *

- * A list of objects that identify the releases associated with this Open - * Contracting ID. The releases MUST be sorted into date order in the array, - * from oldest (at position 0) to newest (last). (Required) - * + * An array of linking identifiers or releases + * (Required) */ @JsonProperty("releases") + @JsonPropertyDescription("An array of linking identifiers or releases") private List releases = new ArrayList(); - - /** - * this NOT in the OCDS standard, but the standard uses "oneOf" which is - * poorly supported presently - * - * @see https://github.com/joelittlejohn/jsonschema2pojo/wiki/Proposal-for- - * allOf,-anyOf-and-oneOf - */ - - private List releaseReferences = new ArrayList(); - /** * Schema for an Open Contracting Release *

- * - * */ @JsonProperty("compiledRelease") - @DBRef private Release compiledRelease; + /** + * Schema for a compiled, versioned Open Contracting Release. + *

+ */ + @JsonProperty("versionedRelease") + private Release versionedRelease; /** * Open Contracting ID *

- * A unique identifier that identifies the unique Open Contracting Process. - * For more information see: - * http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/ - * definitions/#contracting-process (Required) - * - * @return The ocid + * A unique identifier that identifies the unique Open Contracting Process. For more information see: + * http://standard.open-contracting.org/latest/en/getting_started/contracting_process/ + * (Required) */ @JsonProperty("ocid") public String getOcid() { @@ -84,38 +73,20 @@ public String getOcid() { /** * Open Contracting ID *

- * A unique identifier that identifies the unique Open Contracting Process. - * For more information see: - * http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/ - * definitions/#contracting-process (Required) - * - * @param ocid - * The ocid + * A unique identifier that identifies the unique Open Contracting Process. For more information see: + * http://standard.open-contracting.org/latest/en/getting_started/contracting_process/ + * (Required) */ @JsonProperty("ocid") - public void setOcid(final String ocid) { + public void setOcid(String ocid) { this.ocid = ocid; } - - @JsonProperty("releaseReferences") - public List getReleaseReferences() { - return releaseReferences; - } - - @JsonProperty("releaseReferences") - public void setReleaseReferences(final List releaseReferences) { - this.releaseReferences = releaseReferences; - } - /** - * Linked releases + * Releases *

- * A list of objects that identify the releases associated with this Open - * Contracting ID. The releases MUST be sorted into date order in the array, - * from oldest (at position 0) to newest (last). (Required) - * - * @return The releases + * An array of linking identifiers or releases + * (Required) */ @JsonProperty("releases") public List getReleases() { @@ -123,26 +94,20 @@ public List getReleases() { } /** - * Linked releases + * Releases *

- * A list of objects that identify the releases associated with this Open - * Contracting ID. The releases MUST be sorted into date order in the array, - * from oldest (at position 0) to newest (last). (Required) - * + * An array of linking identifiers or releases + * (Required) * @param releases - * The releases */ @JsonProperty("releases") - public void setReleases(final List releases) { + public void setReleases(List releases) { this.releases = releases; } /** * Schema for an Open Contracting Release *

- * - * - * @return The compiledRelease */ @JsonProperty("compiledRelease") public Release getCompiledRelease() { @@ -152,31 +117,50 @@ public Release getCompiledRelease() { /** * Schema for an Open Contracting Release *

- * - * - * @param compiledRelease - * The compiledRelease */ @JsonProperty("compiledRelease") - public void setCompiledRelease(final Release compiledRelease) { + public void setCompiledRelease(Release compiledRelease) { this.compiledRelease = compiledRelease; } + /** + * Schema for a compiled, versioned Open Contracting Release. + *

+ */ + @JsonProperty("versionedRelease") + public Release getVersionedRelease() { + return versionedRelease; + } + + /** + * Schema for a compiled, versioned Open Contracting Release. + *

+ */ + @JsonProperty("versionedRelease") + public void setVersionedRelease(Release versionedRelease) { + this.versionedRelease = versionedRelease; + } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("ocid", ocid) + .append("releases", releases) + .append("compiledRelease", compiledRelease) + .append("versionedRelease", versionedRelease) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(ocid). - append(releases). - append(compiledRelease).toHashCode(); + return new HashCodeBuilder().append(compiledRelease) + .append(versionedRelease) + .append(ocid) + .append(releases) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -184,15 +168,11 @@ public boolean equals(final Object other) { return false; } Record rhs = ((Record) other); - return new EqualsBuilder(). - append(ocid, rhs.ocid). - append(releases, rhs.releases). - append(compiledRelease, rhs.compiledRelease).isEquals(); - } - - @Override - public Serializable getIdProperty() { - return ocid; + return new EqualsBuilder().append(compiledRelease, rhs.compiledRelease) + .append(versionedRelease, rhs.versionedRelease) + .append(ocid, rhs.ocid) + .append(releases, rhs.releases) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RecordPackage.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RecordPackage.java index 82c598077..ea31bf83e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RecordPackage.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RecordPackage.java @@ -1,31 +1,34 @@ - package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; -import org.springframework.data.mongodb.core.mapping.Document; -import java.io.Serializable; +import java.net.URI; +import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; + /** * Schema for an Open Contracting Record package *

- * The record package contains a list of records along with some publishing meta data. - * The records pull together all the releases under a single Open Contracting ID and compile them - * into the latest version of the information along with the history of any data changes. - * + * The record package contains a list of records along with some publishing metadata. The records pull together all + * the releases under a single Open Contracting ID and compile them into the latest version of the information along + * with the history of any data changes. */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "uri", + "version", + "extensions", "publisher", "license", "publicationPolicy", @@ -33,103 +36,165 @@ "packages", "records" }) -@Document -public class RecordPackage implements Identifiable { - +public class RecordPackage { /** - * Package Identifier + * Package identifier *

- * The String of this package that identifies it uniquely in the world. + * The URI of this package that identifies it uniquely in the world. * (Required) - * */ @JsonProperty("uri") - @Id - private String uri; - + @JsonPropertyDescription("The URI of this package that identifies it uniquely in the world.") + private URI uri; + /** + * OCDS schema version + *

+ * The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1 + * (Required) + */ + @JsonProperty("version") + @JsonPropertyDescription("The version of the OCDS schema used in this package, expressed as major.minor For " + + "example: 1.0 or 1.1") + private String version; + /** + * OCDS extensions + *

+ * An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for + * that extension. + */ + @JsonProperty("extensions") + @JsonPropertyDescription("An array of OCDS extensions used in this package. Each entry should be a URL to the " + + "extension.json file for that extension.") + private List extensions = new ArrayList(); /** * Information to uniquely identify the publisher of this package. * (Required) - * */ @JsonProperty("publisher") + @JsonPropertyDescription("Information to uniquely identify the publisher of this package.") private Publisher publisher; - /** - * A link to the license that applies to the data in this datapackage. [Open - * Definition Conformant](http://opendefinition.org/licenses/) licenses are - * strongly recommended. The canonical String of the license should be used. - * Documents linked from this file may be under other license conditions. - * + * License + *

+ * A link to the license that applies to the data in this data package. [Open Definition Conformant] + * (http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license + * should be used. Documents linked from this file may be under other license conditions. */ @JsonProperty("license") - private String license; - + @JsonPropertyDescription("A link to the license that applies to the data in this data package. [Open Definition " + + "Conformant](http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI " + + "of the license should be used. Documents linked from this file may be under other license conditions.") + private URI license; /** + * Publication policy + *

* A link to a document describing the publishers publication policy. - * */ @JsonProperty("publicationPolicy") - private String publicationPolicy; - + @JsonPropertyDescription("A link to a document describing the publishers publication policy.") + private URI publicationPolicy; /** - * The date that this package was published. (Required) - * + * Published date + *

+ * The date that this package was published. If this package is generated 'on demand', this date should reflect + * the date of the last change to the underlying contents of the package. + * (Required) */ @JsonProperty("publishedDate") + @JsonPropertyDescription("The date that this package was published. If this package is generated 'on demand', " + + "this date should reflect the date of the last change to the underlying contents of the package.") private Date publishedDate; - /** - * A list of Strings of all the release packages that were used to create - * this record package. (Required) - * + * Packages + *

+ * A list of URIs of all the release packages that were used to create this record package. */ @JsonProperty("packages") @JsonDeserialize(as = java.util.LinkedHashSet.class) - private Set packages = new LinkedHashSet(); - + @JsonPropertyDescription("A list of URIs of all the release packages that were used to create this record package.") + private Set packages = new LinkedHashSet(); /** - * The records for this data package. (Required) - * + * Records + *

+ * The records for this data package. + * (Required) */ @JsonProperty("records") @JsonDeserialize(as = java.util.LinkedHashSet.class) - @DBRef + @JsonPropertyDescription("The records for this data package.") private Set records = new LinkedHashSet(); + /** - * Package Identifier + * Package identifier *

- * The String of this package that identifies it uniquely in the world. + * The URI of this package that identifies it uniquely in the world. * (Required) - * - * @return The uri */ @JsonProperty("uri") - public String getUri() { + public URI getUri() { return uri; } /** - * Package Identifier + * Package identifier *

- * The String of this package that identifies it uniquely in the world. + * The URI of this package that identifies it uniquely in the world. * (Required) - * - * @param uri - * The uri */ @JsonProperty("uri") - public void setUri(final String uri) { + public void setUri(URI uri) { this.uri = uri; } + /** + * OCDS schema version + *

+ * The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1 + * (Required) + */ + @JsonProperty("version") + public String getVersion() { + return version; + } + + /** + * OCDS schema version + *

+ * The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1 + * (Required) + */ + @JsonProperty("version") + public void setVersion(String version) { + this.version = version; + } + + /** + * OCDS extensions + *

+ * An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for + * that extension. + */ + @JsonProperty("extensions") + public List getExtensions() { + return extensions; + } + + /** + * OCDS extensions + *

+ * An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for + * that extension. + */ + @JsonProperty("extensions") + public void setExtensions(List extensions) { + this.extensions = extensions; + } + /** * Information to uniquely identify the publisher of this package. * (Required) - * - * @return The publisher */ @JsonProperty("publisher") public Publisher getPublisher() { @@ -139,67 +204,62 @@ public Publisher getPublisher() { /** * Information to uniquely identify the publisher of this package. * (Required) - * - * @param publisher - * The publisher */ @JsonProperty("publisher") - public void setPublisher(final Publisher publisher) { + public void setPublisher(Publisher publisher) { this.publisher = publisher; } /** - * A link to the license that applies to the data in this datapackage. [Open - * Definition Conformant](http://opendefinition.org/licenses/) licenses are - * strongly recommended. The canonical String of the license should be used. - * Documents linked from this file may be under other license conditions. - * - * @return The license + * License + *

+ * A link to the license that applies to the data in this data package. [Open Definition Conformant] + * (http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license + * should be used. Documents linked from this file may be under other license conditions. */ @JsonProperty("license") - public String getLicense() { + public URI getLicense() { return license; } /** - * A link to the license that applies to the data in this datapackage. [Open - * Definition Conformant](http://opendefinition.org/licenses/) licenses are - * strongly recommended. The canonical String of the license should be used. - * Documents linked from this file may be under other license conditions. - * - * @param license - * The license + * License + *

+ * A link to the license that applies to the data in this data package. [Open Definition Conformant] + * (http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license + * should be used. Documents linked from this file may be under other license conditions. */ @JsonProperty("license") - public void setLicense(final String license) { + public void setLicense(URI license) { this.license = license; } /** + * Publication policy + *

* A link to a document describing the publishers publication policy. - * - * @return The publicationPolicy */ @JsonProperty("publicationPolicy") - public String getPublicationPolicy() { + public URI getPublicationPolicy() { return publicationPolicy; } /** + * Publication policy + *

* A link to a document describing the publishers publication policy. - * - * @param publicationPolicy - * The publicationPolicy */ @JsonProperty("publicationPolicy") - public void setPublicationPolicy(final String publicationPolicy) { + public void setPublicationPolicy(URI publicationPolicy) { this.publicationPolicy = publicationPolicy; } /** - * The date that this package was published. (Required) - * - * @return The publishedDate + * Published date + *

+ * The date that this package was published. If this package is generated 'on demand', this date should reflect + * the date of the last change to the underlying contents of the package. + * (Required) */ @JsonProperty("publishedDate") public Date getPublishedDate() { @@ -207,43 +267,42 @@ public Date getPublishedDate() { } /** - * The date that this package was published. (Required) - * - * @param publishedDate - * The publishedDate + * Published date + *

+ * The date that this package was published. If this package is generated 'on demand', this date should reflect + * the date of the last change to the underlying contents of the package. + * (Required) */ @JsonProperty("publishedDate") - public void setPublishedDate(final Date publishedDate) { + public void setPublishedDate(Date publishedDate) { this.publishedDate = publishedDate; } /** - * A list of Strings of all the release packages that were used to create - * this record package. (Required) - * - * @return The packages + * Packages + *

+ * A list of URIs of all the release packages that were used to create this record package. */ @JsonProperty("packages") - public Set getPackages() { + public Set getPackages() { return packages; } /** - * A list of Strings of all the release packages that were used to create - * this record package. (Required) - * - * @param packages - * The packages + * Packages + *

+ * A list of URIs of all the release packages that were used to create this record package. */ @JsonProperty("packages") - public void setPackages(final Set packages) { + public void setPackages(Set packages) { this.packages = packages; } /** - * The records for this data package. (Required) - * - * @return The records + * Records + *

+ * The records for this data package. + * (Required) */ @JsonProperty("records") public Set getRecords() { @@ -251,35 +310,46 @@ public Set getRecords() { } /** - * The records for this data package. (Required) - * - * @param records - * The records + * Records + *

+ * The records for this data package. + * (Required) */ @JsonProperty("records") - public void setRecords(final Set records) { + public void setRecords(Set records) { this.records = records; } @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("uri", uri) + .append("version", version) + .append("extensions", extensions) + .append("publisher", publisher) + .append("license", license) + .append("publicationPolicy", publicationPolicy) + .append("publishedDate", publishedDate) + .append("packages", packages) + .append("records", records) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(uri). - append(publisher). - append(license). - append(publicationPolicy). - append(publishedDate). - append(packages). - append(records).toHashCode(); + return new HashCodeBuilder().append(publicationPolicy) + .append(license) + .append(extensions) + .append(records) + .append(publisher) + .append(publishedDate) + .append(packages) + .append(uri) + .append(version) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -287,19 +357,16 @@ public boolean equals(final Object other) { return false; } RecordPackage rhs = ((RecordPackage) other); - return new EqualsBuilder(). - append(uri, rhs.uri). - append(publisher, rhs.publisher). - append(license, rhs.license). - append(publicationPolicy, rhs.publicationPolicy). - append(publishedDate, rhs.publishedDate). - append(packages, rhs.packages). - append(records, rhs.records).isEquals(); - } - - @Override - public Serializable getIdProperty() { - return uri; + return new EqualsBuilder().append(publicationPolicy, rhs.publicationPolicy) + .append(license, rhs.license) + .append(extensions, rhs.extensions) + .append(records, rhs.records) + .append(publisher, rhs.publisher) + .append(publishedDate, rhs.publishedDate) + .append(packages, rhs.packages) + .append(uri, rhs.uri) + .append(version, rhs.version) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RelatedProcess.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RelatedProcess.java new file mode 100644 index 000000000..311a65dad --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RelatedProcess.java @@ -0,0 +1,267 @@ +package org.devgateway.ocds.persistence.mongo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + + +/** + * Related Process + *

+ * A reference to a related contracting process: generally one preceding or following on from the current process. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "id", + "relationship", + "title", + "scheme", + "identifier", + "uri" +}) +public class RelatedProcess { + + /** + * Relationship ID + *

+ * A local identifier for this relationship, unique within this array. + */ + @JsonProperty("id") + @JsonPropertyDescription("A local identifier for this relationship, unique within this array.") + private String id; + /** + * Relationship + *

+ * Specify the type of relationship using the [related process codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#related-process). + */ + @JsonProperty("relationship") + @JsonPropertyDescription("Specify the type of relationship using the [related process codelist](http://standard" + + ".open-contracting.org/latest/en/schema/codelists/#related-process).") + private List relationship = new ArrayList(); + /** + * Related process title + *

+ * The title of the related process, where referencing an open contracting process, this field should match the + * tender/title field in the related process. + */ + @JsonProperty("title") + @JsonPropertyDescription("The title of the related process, where referencing an open contracting process, this " + + "field should match the tender/title field in the related process.") + private String title; + /** + * Scheme + *

+ * The identification scheme used by this cross-reference from the [related process scheme codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#related-process-scheme) codelist. When + * cross-referencing information also published using OCDS, an Open Contracting ID (ocid) should be used. + */ + @JsonProperty("scheme") + @JsonPropertyDescription("The identification scheme used by this cross-reference from the [related process scheme" + + " codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#related-process-scheme) " + + "codelist. When cross-referencing information also published using OCDS, an Open Contracting ID (ocid) " + + "should be used.") + + private String scheme; + /** + * Identifier + *

+ * The identifier of the related process. When cross-referencing information also published using OCDS, this + * should be the Open Contracting ID (ocid). + */ + @JsonProperty("identifier") + @JsonPropertyDescription("The identifier of the related process. When cross-referencing information also " + + "published using OCDS, this should be the Open Contracting ID (ocid).") + private String identifier; + /** + * Related process URI + *

+ * A URI pointing to a machine-readable document, release or record package containing the identified related + * process. + */ + @JsonProperty("uri") + @JsonPropertyDescription("A URI pointing to a machine-readable document, release or record package containing the" + + " identified related process.") + private URI uri; + + /** + * Relationship ID + *

+ * A local identifier for this relationship, unique within this array. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * Relationship ID + *

+ * A local identifier for this relationship, unique within this array. + */ + @JsonProperty("id") + public void setId(String id) { + this.id = id; + } + + /** + * Relationship + *

+ * Specify the type of relationship using the [related process codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#related-process). + */ + @JsonProperty("relationship") + public List getRelationship() { + return relationship; + } + + /** + * Relationship + *

+ * Specify the type of relationship using the [related process codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#related-process). + */ + @JsonProperty("relationship") + public void setRelationship(List relationship) { + this.relationship = relationship; + } + + /** + * Related process title + *

+ * The title of the related process, where referencing an open contracting process, this field should match the + * tender/title field in the related process. + */ + @JsonProperty("title") + public String getTitle() { + return title; + } + + /** + * Related process title + *

+ * The title of the related process, where referencing an open contracting process, this field should match the + * tender/title field in the related process. + */ + @JsonProperty("title") + public void setTitle(String title) { + this.title = title; + } + + /** + * Scheme + *

+ * The identification scheme used by this cross-reference from the [related process scheme codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#related-process-scheme) codelist. When + * cross-referencing information also published using OCDS, an Open Contracting ID (ocid) should be used. + */ + @JsonProperty("scheme") + public String getScheme() { + return scheme; + } + + /** + * Scheme + *

+ * The identification scheme used by this cross-reference from the [related process scheme codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#related-process-scheme) codelist. When + * cross-referencing information also published using OCDS, an Open Contracting ID (ocid) should be used. + */ + @JsonProperty("scheme") + public void setScheme(String scheme) { + this.scheme = scheme; + } + + /** + * Identifier + *

+ * The identifier of the related process. When cross-referencing information also published using OCDS, this + * should be the Open Contracting ID (ocid). + */ + @JsonProperty("identifier") + public String getIdentifier() { + return identifier; + } + + /** + * Identifier + *

+ * The identifier of the related process. When cross-referencing information also published using OCDS, this + * should be the Open Contracting ID (ocid). + */ + @JsonProperty("identifier") + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Related process URI + *

+ * A URI pointing to a machine-readable document, release or record package containing the identified related + * process. + */ + @JsonProperty("uri") + public URI getUri() { + return uri; + } + + /** + * Related process URI + *

+ * A URI pointing to a machine-readable document, release or record package containing the identified related + * process. + */ + @JsonProperty("uri") + public void setUri(URI uri) { + this.uri = uri; + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("id", id) + .append("relationship", relationship) + .append("title", title) + .append("scheme", scheme) + .append("identifier", identifier) + .append("uri", uri) + .toString(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(identifier) + .append(scheme) + .append(id) + .append(relationship) + .append(title) + .append(uri) + .toHashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof RelatedProcess)) { + return false; + } + RelatedProcess rhs = ((RelatedProcess) other); + return new EqualsBuilder().append(identifier, rhs.identifier) + .append(scheme, rhs.scheme) + .append(id, rhs.id) + .append(relationship, rhs.relationship) + .append(title, rhs.title) + .append(uri, rhs.uri) + .isEquals(); + } + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Release.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Release.java index d2915be20..22b71de2e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Release.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Release.java @@ -1,6 +1,7 @@ package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @@ -11,11 +12,6 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExportSepareteSheet; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; import java.io.Serializable; import java.util.ArrayList; @@ -26,199 +22,226 @@ import java.util.Map; import java.util.Set; + /** * Schema for an Open Contracting Release *

- * - * http://standard.open-contracting.org/latest/en/schema/release/ - * */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ - "id", "ocid", + "id", "date", "tag", "initiationType", + "parties", + "buyer", + "bids", "planning", "tender", - "bids", - "buyer", "awards", "contracts", - "language" + "language", + "relatedProcesses" }) -@Document public class Release implements Identifiable { + /** * Release ID *

- * A unique identifier that identifies this release. A releaseID must be unique within a release-package - * and must not contain the # character. + * An identifier for this particular release of information. A release identifier must be unique within the scope + * of its related contracting process (defined by a common ocid), and unique within any release package it + * appears in. A release identifier must not contain the # character. * (Required) - * */ - @ExcelExport @JsonProperty("id") - @Id - @Merge(MergeStrategy.ocdsOmit) + @JsonPropertyDescription("An identifier for this particular release of information. A release identifier must be " + + "unique within the scope of its related contracting process (defined by a common ocid), and unique " + + "within any release package it appears in. A release identifier must not contain the # character.") + @ExcelExport private String id; /** * Open Contracting ID *

- * A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and - * an identifier for the contracting process. For more information see the - * [Open Contracting Identifier guidance] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#ocid) + * A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an + * identifier for the contracting process. For more information see the [Open Contracting Identifier guidance] + * (http://standard.open-contracting.org/latest/en/schema/identifiers/) * (Required) - * */ - @ExcelExport @JsonProperty("ocid") - @Merge(MergeStrategy.ocdsOmit) + @JsonPropertyDescription("A globally unique identifier for this Open Contracting Process. Composed of a publisher" + + " prefix and an identifier for the contracting process. For more information see the [Open Contracting " + + "Identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/)") + @ExcelExport private String ocid; /** * Release Date *

- * The date this information is released, it may well be the same as the parent publishedDate, - * it must not be later than the publishedDate from the parent package. It is used to determine merge order. + * The date this information was first released, or published. * (Required) - * */ - @ExcelExport @JsonProperty("date") - @CreatedDate - @Merge(MergeStrategy.ocdsOmit) + @JsonPropertyDescription("The date this information was first released, or published.") + @ExcelExport private Date date; - /** * Release Tag *

- * A value from the - * [releaseTag codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#release-tag) - * that identifies the nature of the release being made. Tags may be used to filter release, or, in future, - * for for advanced validation when certain kinds of releases should contain certain fields. + * One or more values from the [releaseTag codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#release-tag). Tags may be used to filter release and to understand the kind + * of information that a release might contain. * (Required) - * */ - @ExcelExport @JsonProperty("tag") - @Merge(MergeStrategy.ocdsOmit) + @JsonPropertyDescription("One or more values from the [releaseTag codelist](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#release-tag). Tags may be used to filter release and to understand " + + "the kind of information that a release might contain.") + @ExcelExport private List tag = new ArrayList(); - /** * Initiation type *

- * String specifying the type of initiation process used for this contract, taken from the - * [initiationType](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#initiation-type) - * codelist. Currently only tender is supported. + * String specifying the type of initiation process used for this contract, taken from the [initiationType] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#initiation-type) codelist. Currently only + * tender is supported. * (Required) - * */ - @ExcelExport @JsonProperty("initiationType") - @Merge(MergeStrategy.ocdsVersion) - private InitiationType initiationType = InitiationType.tender; - - /** - * Planning - *

- * Information from the planning phase of the contracting process. Note that many other fields may be filled - * in a planning release, in the appropriate fields in other schema sections, these would likely be estimates - * at this stage e.g. totalValue in tender - * - */ + @JsonPropertyDescription("String specifying the type of initiation process used for this contract, taken from the" + + " [initiationType](http://standard.open-contracting.org/latest/en/schema/codelists/#initiation-type) " + + "codelist. Currently only tender is supported.") @ExcelExport - @JsonProperty("planning") - private Planning planning; - + private InitiationType initiationType; /** - * Tender + * Parties *

- * Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation - * and selecting a winner or winners. - * + * Information on the parties (organizations, economic operators and other participants) who are involved in the + * contracting process and their roles, e.g. buyer, procuring entity, supplier etc. Organization references + * elsewhere in the schema are used to refer back to this entries in this list. */ + @JsonProperty("parties") + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("Information on the parties (organizations, economic operators and other participants) " + + "who are involved in the contracting process and their roles, e.g. buyer, procuring entity, supplier " + + "etc. Organization references elsewhere in the schema are used to refer back to this entries in this " + + "list.") @ExcelExport - @ExcelExportSepareteSheet - @JsonProperty("tender") - private Tender tender; + private Set parties = new LinkedHashSet(); + /** * Bids *

* Summary and detailed information about bids received and evaluated as part of this contracting process. - * */ @JsonProperty("bids") - @ExcelExport - @ExcelExportSepareteSheet @JsonPropertyDescription("Summary and detailed information about bids received and evaluated as part" + " of this contracting process.") + @ExcelExport + @ExcelExportSepareteSheet private Bids bids = new Bids(); /** - * Organization + * Organization reference *

- * An organization. - * + * The id and name of the party being referenced. Used to cross-reference to the parties section */ - @ExcelExport @JsonProperty("buyer") + @JsonPropertyDescription("The id and name of the party being referenced. Used to cross-reference to the parties " + + "section") + @ExcelExport private Organization buyer; - /** - * Awards + * Planning *

- * Information from the award phase of the contracting process. There may be more than one award per contracting - * process e.g. because the contract is split amongst different providers, or because it is a standing offer. - * + * Information from the planning phase of the contracting process. Note that many other fields may be filled in a + * planning release, in the appropriate fields in other schema sections, these would likely be estimates at this + * stage e.g. totalValue in tender + */ + @JsonProperty("planning") + @JsonPropertyDescription("Information from the planning phase of the contracting process. Note that many other " + + "fields may be filled in a planning release, in the appropriate fields in other schema sections, these " + + "would likely be estimates at this stage e.g. totalValue in tender") + @ExcelExport + private Planning planning; + /** + * Tender + *

+ * Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and + * selecting a winner or winners. */ + @JsonProperty("tender") + @JsonPropertyDescription("Data regarding tender process - publicly inviting prospective contractors to submit " + + "bids for evaluation and selecting a winner or winners.") @ExcelExport @ExcelExportSepareteSheet + private Tender tender; + /** + * Awards + *

+ * Information from the award phase of the contracting process. There may be more than one award per contracting + * process e.g. because the contract is split among different providers, or because it is a standing offer. + */ @JsonProperty("awards") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("Information from the award phase of the contracting process. There may be more than one" + + " award per contracting process e.g. because the contract is split among different providers, or " + + "because it is a standing offer.") + @ExcelExportSepareteSheet + @ExcelExport private Set awards = new LinkedHashSet(); - /** * Contracts *

* Information from the contract creation phase of the procurement process. - * */ + @JsonProperty("contracts") @ExcelExport @ExcelExportSepareteSheet - @JsonProperty("contracts") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("Information from the contract creation phase of the procurement process.") private Set contracts = new LinkedHashSet(); - /** * Release language *

- * Specifies the default language of the data using either two-digit ISO 639-1, or extended BCP47 language tags. - * The use of two-letter codes from ISO 639-1 is strongly recommended. - * + * Specifies the default language of the data using either two-letter [ISO639-1](https://en.wikipedia + * .org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www + * .w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1] + * (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended. */ - @ExcelExport @JsonProperty("language") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("Specifies the default language of the data using either two-letter [ISO639-1]" + + "(https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www" + + ".w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1]" + + "(https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended.") private String language = "en"; + /** + * Related processes + *

+ * If this process follows on from one or more prior process, represented under a separate open contracting + * identifier (ocid) then details of the related process can be provided here. This is commonly used to relate + * mini-competitions to their parent frameworks, full tenders to a pre-qualification phase, or individual tenders + * to a broad planning process. + */ + @JsonProperty("relatedProcesses") + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("If this process follows on from one or more prior process, represented under a separate" + + " open contracting identifier (ocid) then details of the related process can be provided here. This is " + + "commonly used to relate mini-competitions to their parent frameworks, full tenders to a " + + "pre-qualification phase, or individual tenders to a broad planning process.") + private Set relatedProcesses = new LinkedHashSet(); /** * Open Contracting ID *

- * A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier - * for the contracting process. For more information see the - * [Open Contracting Identifier guidance] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#ocid) + * A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an + * identifier for the contracting process. For more information see the [Open Contracting Identifier guidance] + * (http://standard.open-contracting.org/latest/en/schema/identifiers/) * (Required) - * - * @return - * The ocid */ @JsonProperty("ocid") public String getOcid() { @@ -228,29 +251,23 @@ public String getOcid() { /** * Open Contracting ID *

- * A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier - * for the contracting process. For more information see the - * [Open Contracting Identifier guidance] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#ocid) + * A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an + * identifier for the contracting process. For more information see the [Open Contracting Identifier guidance] + * (http://standard.open-contracting.org/latest/en/schema/identifiers/) * (Required) - * - * @param ocid - * The ocid */ @JsonProperty("ocid") - public void setOcid(final String ocid) { + public void setOcid(String ocid) { this.ocid = ocid; } /** * Release ID *

- * A unique identifier that identifies this release. A releaseID must be unique within a release-package - * and must not contain the # character. + * An identifier for this particular release of information. A release identifier must be unique within the scope + * of its related contracting process (defined by a common ocid), and unique within any release package it + * appears in. A release identifier must not contain the # character. * (Required) - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -260,27 +277,21 @@ public String getId() { /** * Release ID *

- * A unique identifier that identifies this release. A releaseID must be unique within a release-package - * and must not contain the # character. + * An identifier for this particular release of information. A release identifier must be unique within the scope + * of its related contracting process (defined by a common ocid), and unique within any release package it + * appears in. A release identifier must not contain the # character. * (Required) - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** * Release Date *

- * The date this information is released, it may well be the same as the parent publishedDate, - * it must not be later than the publishedDate from the parent package. It is used to determine merge order. + * The date this information was first released, or published. * (Required) - * - * @return - * The date */ @JsonProperty("date") public Date getDate() { @@ -290,29 +301,21 @@ public Date getDate() { /** * Release Date *

- * The date this information is released, it may well be the same as the parent publishedDate, - * it must not be later than the publishedDate from the parent package. It is used to determine merge order. + * The date this information was first released, or published. * (Required) - * - * @param date - * The date */ @JsonProperty("date") - public void setDate(final Date date) { + public void setDate(Date date) { this.date = date; } /** * Release Tag *

- * A value from the - * [releaseTag codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#release-tag) - * that identifies the nature of the release being made. Tags may be used to filter release, or, in future, - * for for advanced validation when certain kinds of releases should contain certain fields. + * One or more values from the [releaseTag codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#release-tag). Tags may be used to filter release and to understand the kind + * of information that a release might contain. * (Required) - * - * @return - * The tag */ @JsonProperty("tag") public List getTag() { @@ -322,30 +325,23 @@ public List getTag() { /** * Release Tag *

- * A value from the - * [releaseTag codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#release-tag) - * that identifies the nature of the release being made. Tags may be used to filter release, or, in future, - * for for advanced validation when certain kinds of releases should contain certain fields. + * One or more values from the [releaseTag codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#release-tag). Tags may be used to filter release and to understand the kind + * of information that a release might contain. * (Required) - * - * @param tag - * The tag */ @JsonProperty("tag") - public void setTag(final List tag) { + public void setTag(List tag) { this.tag = tag; } /** * Initiation type *

- * String specifying the type of initiation process used for this contract, taken from the - * [initiationType](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#initiation-type) - * codelist. Currently only tender is supported. + * String specifying the type of initiation process used for this contract, taken from the [initiationType] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#initiation-type) codelist. Currently only + * tender is supported. * (Required) - * - * @return - * The initiationType */ @JsonProperty("initiationType") public InitiationType getInitiationType() { @@ -355,133 +351,111 @@ public InitiationType getInitiationType() { /** * Initiation type *

- * String specifying the type of initiation process used for this contract, taken from the - * [initiationType](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#initiation-type) - * codelist. Currently only tender is supported. + * String specifying the type of initiation process used for this contract, taken from the [initiationType] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#initiation-type) codelist. Currently only + * tender is supported. * (Required) - * - * @param initiationType - * The initiationType */ @JsonProperty("initiationType") - public void setInitiationType(final InitiationType initiationType) { + public void setInitiationType(InitiationType initiationType) { this.initiationType = initiationType; } /** - * Planning + * Parties *

- * Information from the planning phase of the contracting process. Note that many other fields may be filled - * in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at - * this stage e.g. totalValue in tender - * - * @return - * The planning + * Information on the parties (organizations, economic operators and other participants) who are involved in the + * contracting process and their roles, e.g. buyer, procuring entity, supplier etc. Organization references + * elsewhere in the schema are used to refer back to this entries in this list. */ - @JsonProperty("planning") - public Planning getPlanning() { - return planning; + @JsonProperty("parties") + public Set getParties() { + return parties; } /** - * Planning + * Parties *

- * Information from the planning phase of the contracting process. Note that many other fields may be filled - * in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at - * this stage e.g. totalValue in tender - * - * @param planning - * The planning + * Information on the parties (organizations, economic operators and other participants) who are involved in the + * contracting process and their roles, e.g. buyer, procuring entity, supplier etc. Organization references + * elsewhere in the schema are used to refer back to this entries in this list. */ - @JsonProperty("planning") - public void setPlanning(final Planning planning) { - this.planning = planning; + @JsonProperty("parties") + public void setParties(Set parties) { + this.parties = parties; } /** - * Tender + * Organization reference *

- * Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and - * selecting a winner or winners. - * - * @return - * The tender + * The id and name of the party being referenced. Used to cross-reference to the parties section */ - @JsonProperty("tender") - public Tender getTender() { - return tender; + @JsonProperty("buyer") + public Organization getBuyer() { + return buyer; } /** - * Tender + * Organization reference *

- * Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and - * selecting a winner or winners. - * - * @param tender - * The tender + * The id and name of the party being referenced. Used to cross-reference to the parties section */ - @JsonProperty("tender") - public void setTender(final Tender tender) { - this.tender = tender; + @JsonProperty("buyer") + public void setBuyer(Organization buyer) { + this.buyer = buyer; } /** - * Bids + * Planning *

- * Summary and detailed information about bids received and evaluated as part of this contracting process. - * + * Information from the planning phase of the contracting process. Note that many other fields may be filled in a + * planning release, in the appropriate fields in other schema sections, these would likely be estimates at this + * stage e.g. totalValue in tender */ - @JsonProperty("bids") - public Bids getBids() { - return bids; + @JsonProperty("planning") + public Planning getPlanning() { + return planning; } /** - * Bids + * Planning *

- * Summary and detailed information about bids received and evaluated as part of this contracting process. - * + * Information from the planning phase of the contracting process. Note that many other fields may be filled in a + * planning release, in the appropriate fields in other schema sections, these would likely be estimates at this + * stage e.g. totalValue in tender */ - @JsonProperty("bids") - public void setBids(Bids bids) { - this.bids = bids; + @JsonProperty("planning") + public void setPlanning(Planning planning) { + this.planning = planning; } /** - * Organization + * Tender *

- * An organization. - * - * @return - * The buyer + * Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and + * selecting a winner or winners. */ - @JsonProperty("buyer") - public Organization getBuyer() { - return buyer; + @JsonProperty("tender") + public Tender getTender() { + return tender; } /** - * Organization + * Tender *

- * An organization. - * - * @param buyer - * The buyer + * Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and + * selecting a winner or winners. */ - @JsonProperty("buyer") - public void setBuyer(final Organization buyer) { - this.buyer = buyer; + @JsonProperty("tender") + public void setTender(Tender tender) { + this.tender = tender; } /** * Awards *

* Information from the award phase of the contracting process. There may be more than one award per contracting - * process e.g. because the contract is split amongst different providers, or because it is a standing offer. - * - * @return - * The awards + * process e.g. because the contract is split among different providers, or because it is a standing offer. */ @JsonProperty("awards") public Set getAwards() { @@ -492,13 +466,10 @@ public Set getAwards() { * Awards *

* Information from the award phase of the contracting process. There may be more than one award per contracting - * process e.g. because the contract is split amongst different providers, or because it is a standing offer. - * - * @param awards - * The awards + * process e.g. because the contract is split among different providers, or because it is a standing offer. */ @JsonProperty("awards") - public void setAwards(final Set awards) { + public void setAwards(Set awards) { this.awards = awards; } @@ -506,9 +477,6 @@ public void setAwards(final Set awards) { * Contracts *

* Information from the contract creation phase of the procurement process. - * - * @return - * The contracts */ @JsonProperty("contracts") public Set getContracts() { @@ -519,23 +487,39 @@ public Set getContracts() { * Contracts *

* Information from the contract creation phase of the procurement process. - * - * @param contracts - * The contracts */ @JsonProperty("contracts") - public void setContracts(final Set contracts) { + public void setContracts(Set contracts) { this.contracts = contracts; } + /** + * Bids + *

+ * Summary and detailed information about bids received and evaluated as part of this contracting process. + */ + @JsonProperty("bids") + public Bids getBids() { + return bids; + } + + /** + * Bids + *

+ * Summary and detailed information about bids received and evaluated as part of this contracting process. + */ + @JsonProperty("bids") + public void setBids(Bids bids) { + this.bids = bids; + } + /** * Release language *

- * Specifies the default language of the data using either two-digit ISO 639-1, or extended BCP47 language tags. - * The use of two-letter codes from ISO 639-1 is strongly recommended. - * - * @return - * The language + * Specifies the default language of the data using either two-letter [ISO639-1](https://en.wikipedia + * .org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www + * .w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1] + * (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended. */ @JsonProperty("language") public String getLanguage() { @@ -545,41 +529,82 @@ public String getLanguage() { /** * Release language *

- * Specifies the default language of the data using either two-digit ISO 639-1, or extended BCP47 language tags. - * The use of two-letter codes from ISO 639-1 is strongly recommended. - * - * @param language - * The language + * Specifies the default language of the data using either two-letter [ISO639-1](https://en.wikipedia + * .org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www + * .w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1] + * (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended. */ @JsonProperty("language") - public void setLanguage(final String language) { + public void setLanguage(String language) { this.language = language; } + /** + * Related processes + *

+ * If this process follows on from one or more prior process, represented under a separate open contracting + * identifier (ocid) then details of the related process can be provided here. This is commonly used to relate + * mini-competitions to their parent frameworks, full tenders to a pre-qualification phase, or individual tenders + * to a broad planning process. + */ + @JsonProperty("relatedProcesses") + public Set getRelatedProcesses() { + return relatedProcesses; + } + + /** + * Related processes + *

+ * If this process follows on from one or more prior process, represented under a separate open contracting + * identifier (ocid) then details of the related process can be provided here. This is commonly used to relate + * mini-competitions to their parent frameworks, full tenders to a pre-qualification phase, or individual tenders + * to a broad planning process. + */ + @JsonProperty("relatedProcesses") + public void setRelatedProcesses(Set relatedProcesses) { + this.relatedProcesses = relatedProcesses; + } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("ocid", ocid) + .append("id", id) + .append("date", date) + .append("tag", tag) + .append("initiationType", initiationType) + .append("parties", parties) + .append("buyer", buyer) + .append("planning", planning) + .append("tender", tender) + .append("awards", awards) + .append("contracts", contracts) + .append("language", language) + .append("relatedProcesses", relatedProcesses) + .append("bids", bids) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(ocid). - append(id). - append(date). - append(tag). - append(initiationType). - append(planning). - append(tender). - append(buyer). - append(awards). - append(contracts). - append(language). - toHashCode(); + return new HashCodeBuilder().append(date) + .append(tender) + .append(relatedProcesses) + .append(language) + .append(contracts) + .append(buyer) + .append(initiationType) + .append(planning) + .append(awards) + .append(parties) + .append(id) + .append(tag) + .append(ocid) + .append(bids) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -587,47 +612,56 @@ public boolean equals(final Object other) { return false; } Release rhs = ((Release) other); - return new EqualsBuilder(). - append(ocid, rhs.ocid). - append(id, rhs.id). - append(date, rhs.date). - append(tag, rhs.tag). - append(initiationType, rhs.initiationType). - append(planning, rhs.planning). - append(tender, rhs.tender). - append(buyer, rhs.buyer). - append(awards, rhs.awards). - append(contracts, rhs.contracts). - append(language, rhs.language). - append(bids, rhs.bids). - isEquals(); + return new EqualsBuilder().append(date, rhs.date) + .append(tender, rhs.tender) + .append(relatedProcesses, rhs.relatedProcesses) + .append(language, rhs.language) + .append(bids, rhs.bids) + .append(contracts, rhs.contracts) + .append(buyer, rhs.buyer) + .append(initiationType, rhs.initiationType) + .append(planning, rhs.planning) + .append(awards, rhs.awards) + .append(parties, rhs.parties) + .append(id, rhs.id) + .append(tag, rhs.tag) + .append(ocid, rhs.ocid) + .isEquals(); + } + + @Override + public Serializable getIdProperty() { + return id; } public enum InitiationType { - tender("tender"); + tender("tender"); private final String value; - - private static final Map CONSTANTS = new HashMap<>(); + private static final Map CONSTANTS = new HashMap(); static { - for (InitiationType c: values()) { + for (InitiationType c : values()) { CONSTANTS.put(c.value, c); } } - InitiationType(final String value) { + InitiationType(String value) { this.value = value; } - @JsonValue @Override public String toString() { return this.value; } + @JsonValue + public String value() { + return this.value; + } + @JsonCreator - public static InitiationType fromValue(final String value) { + public static InitiationType fromValue(String value) { InitiationType constant = CONSTANTS.get(value); if (constant == null) { throw new IllegalArgumentException(value); @@ -635,11 +669,7 @@ public static InitiationType fromValue(final String value) { return constant; } } - } - @Override - public Serializable getIdProperty() { - return id; } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleasePackage.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleasePackage.java index de06c4a89..8e7c9a530 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleasePackage.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleasePackage.java @@ -1,128 +1,200 @@ package org.devgateway.ocds.persistence.mongo; + +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; -import org.springframework.data.mongodb.core.mapping.Document; import java.io.Serializable; +import java.net.URI; +import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; + /** * Schema for an Open Contracting Release Package *

- * Note that all releases within a release package must have a unique releaseID - * within this release package. - * + * Note that all releases within a release package must have a unique releaseID within this release package. */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "uri", + "version", + "extensions", "publishedDate", "releases", "publisher", "license", "publicationPolicy" }) -@Document public class ReleasePackage implements Identifiable { - /** - * Package Identifier + * Package identifier *

- * The String of this package that identifies it uniquely in the world. + * The URI of this package that identifies it uniquely in the world. Recommended practice is to use a + * dereferenceable URI, where a persistent copy of this package is available. * (Required) - * */ @JsonProperty("uri") - @Id - private String uri; - + @JsonPropertyDescription("The URI of this package that identifies it uniquely in the world. Recommended practice " + + "is to use a dereferenceable URI, where a persistent copy of this package is available.") + private URI uri; + /** + * OCDS schema version + *

+ * The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1 + * (Required) + */ + @JsonProperty("version") + @JsonPropertyDescription("The version of the OCDS schema used in this package, expressed as major.minor For " + + "example: 1.0 or 1.1") + private String version; + /** + * OCDS extensions + *

+ * An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for + * that extension. + */ + @JsonProperty("extensions") + @JsonPropertyDescription("An array of OCDS extensions used in this package. Each entry should be a URL to the " + + "extension.json file for that extension.") + private List extensions = new ArrayList(); /** - * The date that this package was published. Ideally this should be the - * latest date that there is release information in this package. (Required) - * + * Published date + *

+ * The date that this package was published. If this package is generated 'on demand', this date should reflect + * the date of the last change to the underlying contents of the package. + * (Required) */ @JsonProperty("publishedDate") + @JsonPropertyDescription("The date that this package was published. If this package is generated 'on demand', " + + "this date should reflect the date of the last change to the underlying contents of the package.") private Date publishedDate; - /** - * + * Releases + *

+ *

* (Required) - * */ @JsonProperty("releases") @JsonDeserialize(as = java.util.LinkedHashSet.class) - @DBRef private Set releases = new LinkedHashSet(); - /** + * Publisher + *

* Information to uniquely identify the publisher of this package. * (Required) - * */ @JsonProperty("publisher") + @JsonPropertyDescription("Information to uniquely identify the publisher of this package.") private Publisher publisher; - /** - * A link to the license that applies to the data in this datapackage. A - * Public Domain Dedication or [Open Definition - * Conformant](http://opendefinition.org/licenses/) license is strongly - * recommended. The canonical String of the license should be used. - * Documents linked from this file may be under other license conditions. - * + * License + *

+ * A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition + * Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the + * license should be used. Documents linked from this file may be under other license conditions. */ @JsonProperty("license") - private String license; - + @JsonPropertyDescription("A link to the license that applies to the data in this package. A Public Domain " + + "Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is strongly " + + "recommended. The canonical URI of the license should be used. Documents linked from this file may be " + + "under other license conditions. ") + private URI license; /** - * A link to a document describing the publishers [publication - * policy](http://ocds.open-contracting.org/standard/r/1__0__0/en/ - * implementation/publication_patterns/#publication-policy). - * + * Publication policy + *

+ * A link to a document describing the publishers [publication policy](http://standard.open-contracting + * .org/latest/en/implementation/publication_policy/). */ @JsonProperty("publicationPolicy") - private String publicationPolicy; + @JsonPropertyDescription("A link to a document describing the publishers [publication policy](http://standard" + + ".open-contracting.org/latest/en/implementation/publication_policy/).") + private URI publicationPolicy; /** - * Package Identifier + * Package identifier *

- * The String of this package that identifies it uniquely in the world. + * The URI of this package that identifies it uniquely in the world. Recommended practice is to use a + * dereferenceable URI, where a persistent copy of this package is available. * (Required) - * - * @return The uri */ @JsonProperty("uri") - public String getUri() { + public URI getUri() { return uri; } /** - * Package Identifier + * Package identifier *

- * The String of this package that identifies it uniquely in the world. + * The URI of this package that identifies it uniquely in the world. Recommended practice is to use a + * dereferenceable URI, where a persistent copy of this package is available. * (Required) - * - * @param uri - * The uri */ @JsonProperty("uri") - public void setUri(final String uri) { + public void setUri(URI uri) { this.uri = uri; } /** - * The date that this package was published. Ideally this should be the - * latest date that there is release information in this package. (Required) - * - * @return The publishedDate + * OCDS schema version + *

+ * The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1 + * (Required) + */ + @JsonProperty("version") + public String getVersion() { + return version; + } + + /** + * OCDS schema version + *

+ * The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1 + * (Required) + */ + @JsonProperty("version") + public void setVersion(String version) { + this.version = version; + } + + /** + * OCDS extensions + *

+ * An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for + * that extension. + */ + @JsonProperty("extensions") + public List getExtensions() { + return extensions; + } + + /** + * OCDS extensions + *

+ * An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for + * that extension. + */ + @JsonProperty("extensions") + public void setExtensions(List extensions) { + this.extensions = extensions; + } + + /** + * Published date + *

+ * The date that this package was published. If this package is generated 'on demand', this date should reflect + * the date of the last change to the underlying contents of the package. + * (Required) */ @JsonProperty("publishedDate") public Date getPublishedDate() { @@ -130,22 +202,22 @@ public Date getPublishedDate() { } /** - * The date that this package was published. Ideally this should be the - * latest date that there is release information in this package. (Required) - * - * @param publishedDate - * The publishedDate + * Published date + *

+ * The date that this package was published. If this package is generated 'on demand', this date should reflect + * the date of the last change to the underlying contents of the package. + * (Required) */ @JsonProperty("publishedDate") - public void setPublishedDate(final Date publishedDate) { + public void setPublishedDate(Date publishedDate) { this.publishedDate = publishedDate; } /** - * + * Releases + *

+ *

* (Required) - * - * @return The releases */ @JsonProperty("releases") public Set getReleases() { @@ -153,112 +225,113 @@ public Set getReleases() { } /** - * Information to uniquely identify the publisher of this package. + * Releases + *

+ *

* (Required) - * - * @return The publisher */ - @JsonProperty("publisher") - public Publisher getPublisher() { - return publisher; + @JsonProperty("releases") + public void setReleases(Set releases) { + this.releases = releases; } /** - * + * Publisher + *

+ * Information to uniquely identify the publisher of this package. * (Required) - * - * @param releases - * The releases */ - @JsonProperty("releases") - public void setReleases(final Set releases) { - this.releases = releases; + @JsonProperty("publisher") + public Publisher getPublisher() { + return publisher; } /** + * Publisher + *

* Information to uniquely identify the publisher of this package. * (Required) - * - * @param publisher - * The publisher */ @JsonProperty("publisher") - public void setPublisher(final Publisher publisher) { + public void setPublisher(Publisher publisher) { this.publisher = publisher; } /** - * A link to the license that applies to the data in this datapackage. A - * Public Domain Dedication or [Open Definition - * Conformant](http://opendefinition.org/licenses/) license is strongly - * recommended. The canonical String of the license should be used. - * Documents linked from this file may be under other license conditions. - * - * @return The license + * License + *

+ * A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition + * Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the + * license should be used. Documents linked from this file may be under other license conditions. */ @JsonProperty("license") - public String getLicense() { + public URI getLicense() { return license; } /** - * A link to the license that applies to the data in this datapackage. A - * Public Domain Dedication or [Open Definition - * Conformant](http://opendefinition.org/licenses/) license is strongly - * recommended. The canonical String of the license should be used. - * Documents linked from this file may be under other license conditions. - * - * @param license - * The license + * License + *

+ * A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition + * Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the + * license should be used. Documents linked from this file may be under other license conditions. */ @JsonProperty("license") - public void setLicense(final String license) { + public void setLicense(URI license) { this.license = license; } /** - * A link to a document describing the publishers [publication - * policy](http://ocds.open-contracting.org/standard/r/1__0__0/en/ - * implementation/publication_patterns/#publication-policy). - * - * @return The publicationPolicy + * Publication policy + *

+ * A link to a document describing the publishers [publication policy](http://standard.open-contracting + * .org/latest/en/implementation/publication_policy/). */ @JsonProperty("publicationPolicy") - public String getPublicationPolicy() { + public URI getPublicationPolicy() { return publicationPolicy; } /** - * A link to a document describing the publishers [publication - * policy](http://ocds.open-contracting.org/standard/r/1__0__0/en/ - * implementation/publication_patterns/#publication-policy). - * - * @param publicationPolicy - * The publicationPolicy + * Publication policy + *

+ * A link to a document describing the publishers [publication policy](http://standard.open-contracting + * .org/latest/en/implementation/publication_policy/). */ @JsonProperty("publicationPolicy") - public void setPublicationPolicy(final String publicationPolicy) { + public void setPublicationPolicy(URI publicationPolicy) { this.publicationPolicy = publicationPolicy; } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("uri", uri) + .append("version", version) + .append("extensions", extensions) + .append("publishedDate", publishedDate) + .append("releases", releases) + .append("publisher", publisher) + .append("license", license) + .append("publicationPolicy", publicationPolicy) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(uri). - append(publishedDate). - append(releases). - append(publisher). - append(license). - append(publicationPolicy).toHashCode(); + return new HashCodeBuilder().append(publicationPolicy) + .append(license) + .append(extensions) + .append(publisher) + .append(publishedDate) + .append(uri) + .append(version) + .append(releases) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -266,18 +339,19 @@ public boolean equals(final Object other) { return false; } ReleasePackage rhs = ((ReleasePackage) other); - return new EqualsBuilder(). - append(uri, rhs.uri). - append(publishedDate, rhs.publishedDate). - append(releases, rhs.releases). - append(publisher, rhs.publisher). - append(license, rhs.license). - append(publicationPolicy, rhs.publicationPolicy).isEquals(); + return new EqualsBuilder().append(publicationPolicy, rhs.publicationPolicy) + .append(license, rhs.license) + .append(extensions, rhs.extensions) + .append(publisher, rhs.publisher) + .append(publishedDate, rhs.publishedDate) + .append(uri, rhs.uri) + .append(version, rhs.version) + .append(releases, rhs.releases) + .isEquals(); } @Override public Serializable getIdProperty() { return uri; } - } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Statistic.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Statistic.java index aff7647da..55a96e5c1 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Statistic.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Statistic.java @@ -1,8 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; @@ -12,8 +9,6 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import java.util.Date; -import java.util.HashMap; -import java.util.Map; /** @@ -94,8 +89,6 @@ public class Statistic { + " the lot identifier here. If left blank, the statistic will be interpreted as applying" + " to the whole tender.") private String relatedLot; - @JsonIgnore - private Map additionalProperties = new HashMap(); /** * ID @@ -236,24 +229,10 @@ public String toString() { return ToStringBuilder.reflectionToString(this); } - @JsonAnyGetter - public Map getAdditionalProperties() { - return this.additionalProperties; - } - - @JsonAnySetter - public void setAdditionalProperty(String name, Object value) { - this.additionalProperties.put(name, value); - } - - public void setAdditionalProperties(Map additionalProperties) { - this.additionalProperties = additionalProperties; - } - @Override public int hashCode() { return new HashCodeBuilder().append(id).append(measure).append(date).append(value).append(notes). - append(relatedLot).append(additionalProperties).toHashCode(); + append(relatedLot).toHashCode(); } @Override @@ -266,8 +245,8 @@ public boolean equals(Object other) { } Statistic rhs = ((Statistic) other); return new EqualsBuilder().append(id, rhs.id).append(measure, rhs.measure).append(date, rhs.date). - append(value, rhs.value).append(notes, rhs.notes).append(relatedLot, rhs.relatedLot). - append(additionalProperties, rhs.additionalProperties).isEquals(); + append(value, rhs.value).append(notes, rhs.notes).append(relatedLot, rhs.relatedLot) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tag.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tag.java index ac43ed8a2..b4ead202e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tag.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tag.java @@ -1,3 +1,4 @@ + package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; @@ -7,60 +8,48 @@ import java.util.Map; public enum Tag { - planning("planning"), + planning("planning"), + planningUpdate("planningUpdate"), tender("tender"), - tenderAmendment("tenderAmendment"), - tenderUpdate("tenderUpdate"), - tenderCancellation("tenderCancellation"), - award("award"), - awardUpdate("awardUpdate"), - awardCancellation("awardCancellation"), - contract("contract"), - contractUpdate("contractUpdate"), - contractAmendment("contractAmendment"), - implementation("implementation"), - implementationUpdate("implementationUpdate"), - contractTermination("contractTermination"), - - bid("bid"), - compiled("compiled"); - private final String value; - private static final Map CONSTANTS = new HashMap(); static { - for (Tag c: values()) { + for (Tag c : values()) { CONSTANTS.put(c.value, c); } } - Tag(final String value) { + Tag(String value) { this.value = value; } - @JsonValue @Override public String toString() { return this.value; } + @JsonValue + public String value() { + return this.value; + } + @JsonCreator - public static Tag fromValue(final String value) { + public static Tag fromValue(String value) { Tag constant = CONSTANTS.get(value); if (constant == null) { throw new IllegalArgumentException(value); diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tender.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tender.java index 654fded9e..f41c0156f 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tender.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tender.java @@ -1,48 +1,47 @@ package org.devgateway.ocds.persistence.mongo; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExportSepareteSheet; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Tender *

- * Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation - * and selecting a winner or winners. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#tender - * + * Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and + * selecting a winner or winners. */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "title", "description", "status", + "procuringEntity", "items", - "minValue", "value", + "minValue", "procurementMethod", + "procurementMethodDetails", "procurementMethodRationale", + "mainProcurementCategory", + "additionalProcurementCategories", "awardCriteria", "awardCriteriaDetails", "submissionMethod", @@ -52,249 +51,327 @@ "hasEnquiries", "eligibilityCriteria", "awardPeriod", + "contractPeriod", "numberOfTenderers", "tenderers", - "procuringEntity", "documents", "milestones", + "amendments", "amendment" }) -public class Tender implements Identifiable { +public class Tender { /** * Tender ID *

- * An identifier for this tender process. This may be the same as the ocid, or may be drawn from - * an internally held identifier for this tender. + * An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally + * held identifier for this tender. * (Required) - * */ - @ExcelExport @JsonProperty("id") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("An identifier for this tender process. This may be the same as the ocid, or may be " + + "drawn from an internally held identifier for this tender.") + @ExcelExport private String id; - /** * Tender title - * + *

+ * A title for this tender. This will often be used by applications as a headline to attract interest, and to + * help analysts understand the nature of this procurement. */ - @ExcelExport @JsonProperty("title") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("A title for this tender. This will often be used by applications as a headline to " + + "attract interest, and to help analysts understand the nature of this procurement.") + @ExcelExport private String title; - /** * Tender description - * + *

+ * A summary description of the tender. This should complement structured information provided using the items + * array. Descriptions should be short and easy to read. Avoid using ALL CAPS. */ - @ExcelExport @JsonProperty("description") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("A summary description of the tender. This should complement structured information " + + "provided using the items array. Descriptions should be short and easy to read. Avoid using ALL CAPS. ") + @ExcelExport private String description; - /** - * Tender Status + * Tender status *

- * The current status of the tender based on the - * [tenderStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#tender-status) - * + * The current status of the tender based on the [tenderStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#tender-status) */ - @ExcelExport @JsonProperty("status") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("The current status of the tender based on the [tenderStatus codelist](http://standard" + + ".open-contracting.org/latest/en/schema/codelists/#tender-status)") + @ExcelExport private Status status; - + /** + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section + */ + @JsonProperty("procuringEntity") + @JsonPropertyDescription("The id and name of the party being referenced. Used to cross-reference to the parties " + + "section") + @ExcelExport + private Organization procuringEntity; /** * Items to be procured *

- * The goods and services to be purchased, broken into line items wherever possible. - * Items should not be duplicated, but a quantity of 2 specified instead. - * + * The goods and services to be purchased, broken into line items wherever possible. Items should not be + * duplicated, but a quantity of 2 specified instead. */ + @JsonProperty("items") @ExcelExport @ExcelExportSepareteSheet - @JsonProperty("items") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.arrayMergeById) + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("The goods and services to be purchased, broken into line items wherever possible. Items" + + " should not be duplicated, but a quantity of 2 specified instead.") private Set items = new LinkedHashSet(); - - @ExcelExport - @JsonProperty("minValue") - private Amount minValue; - - @ExcelExport + /** + * Value + *

+ */ @JsonProperty("value") + @ExcelExport private Amount value; - /** - * Specify tendering method against the - * [method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#method) - * as per [GPA definitions](http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm) of - * Open, Selective, Limited - * + * Value + *

*/ + @JsonProperty("minValue") @ExcelExport + private Amount minValue; + /** + * Procurement method + *

+ * Specify tendering method using the [method codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#method). This is a closed codelist. Local method types should be mapped to + * this list. + */ @JsonProperty("procurementMethod") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Specify tendering method using the [method codelist](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#method). This is a closed codelist. Local method types should be " + + "mapped to this list.") + @ExcelExport private ProcurementMethod procurementMethod; - /** - * Rationale of procurement method, especially in the case of Limited tendering. - * + * Procurement method details + *

+ * Additional detail on the procurement method used. This field may be used to provide the local name of the + * particular procurement method used. + */ + @JsonProperty("procurementMethodDetails") + @JsonPropertyDescription("Additional detail on the procurement method used. This field may be used to provide the" + + " local name of the particular procurement method used.") + private String procurementMethodDetails; + /** + * Procurement method rationale + *

+ * Rationale for the chosen procurement method. This is especially important to provide a justification in the + * case of limited tenders or direct awards. */ - @ExcelExport @JsonProperty("procurementMethodRationale") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Rationale for the chosen procurement method. This is especially important to provide a " + + "justification in the case of limited tenders or direct awards.") + @ExcelExport private String procurementMethodRationale; + /** + * Main procurement category + *

+ * The primary category describing the main object of this contracting process from the [procurementCategory] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#procurement-category) codelist. This is a + * closed codelist. Local classifications should be mapped to this list. + */ + @JsonProperty("mainProcurementCategory") + @JsonPropertyDescription("The primary category describing the main object of this contracting process from the " + + "[procurementCategory](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#procurement-category) codelist. This is a closed codelist. Local " + + "classifications should be mapped to this list.") + private MainProcurementCategory mainProcurementCategory; + /** + * Additional procurement categories + *

+ * Any additional categories which describe the objects of this contracting process, from the + * [extendedProcurementCategory](http://standard.open-contracting + * .org/latest/en/schema/codelists/#extended-procurement-category) codelist. This is an open codelist. Local + * categories can be included in this list. + */ + @JsonProperty("additionalProcurementCategories") + @JsonPropertyDescription("Any additional categories which describe the objects of this contracting process, from " + + "the [extendedProcurementCategory](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#extended-procurement-category) codelist. This is an open codelist. " + + "Local categories can be included in this list.") + private List additionalProcurementCategories = new ArrayList(); /** - * Specify the award criteria for the procurement, using the - * [award criteria codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#award-criteria) - * + * Award criteria + *

+ * Specify the award criteria for the procurement, using the [award criteria codelist](http://standard + * .open-contracting.org/latest/en/schema/codelists/#award-criteria) */ - @ExcelExport @JsonProperty("awardCriteria") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Specify the award criteria for the procurement, using the [award criteria codelist]" + + "(http://standard.open-contracting.org/latest/en/schema/codelists/#award-criteria)") + @ExcelExport private String awardCriteria; - /** + * Award criteria details + *

* Any detailed or further information on the award or selection criteria. - * */ - @ExcelExport @JsonProperty("awardCriteriaDetails") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Any detailed or further information on the award or selection criteria.") private String awardCriteriaDetails; - /** - * Specify the method by which bids must be submitted, in person, written, or electronic auction. - * Using the - * [submission method codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#submission-method) - * + * Submission method + *

+ * Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the + * [submission method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#submission-method) */ - @ExcelExport @JsonProperty("submissionMethod") - @Merge(MergeStrategy.ocdsVersion) - private Set submissionMethod = new TreeSet(); - + @JsonPropertyDescription("Specify the method by which bids must be submitted, in person, written, or electronic " + + "auction. Using the [submission method codelist](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#submission-method)") + @ExcelExport + private List submissionMethod = new ArrayList(); /** - * Any detailed or further information on the submission method. This may include the address, - * e-mail address or online service to which bids should be submitted, - * and any special requirements to be followed for submissions. - * + * Submission method details + *

+ * Any detailed or further information on the submission method. This may include the address, e-mail address or + * online service to which bids should be submitted, and any special requirements to be followed for submissions. */ - @ExcelExport @JsonProperty("submissionMethodDetails") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Any detailed or further information on the submission method. This may include the " + + "address, e-mail address or online service to which bids should be submitted, and any special " + + "requirements to be followed for submissions.") + @ExcelExport private String submissionMethodDetails; - /** * Period *

- * - * */ - @ExcelExport @JsonProperty("tenderPeriod") + @JsonPropertyDescription(" ") + @ExcelExport private Period tenderPeriod; - /** * Period *

- * - * */ @JsonProperty("enquiryPeriod") + @JsonPropertyDescription(" ") + @ExcelExport private Period enquiryPeriod; - /** - * A Yes/No field to indicate whether enquiries were part of tender process. - * + * Has enquiries? + *

+ * A true/false field to indicate whether any enquiries were received during the tender process. Structured + * information on enquiries that were received, and responses to them, can be provided using the enquiries + * extension. */ @JsonProperty("hasEnquiries") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("A true/false field to indicate whether any enquiries were received during the tender " + + "process. Structured information on enquiries that were received, and responses to them, can be " + + "provided using the enquiries extension.") private Boolean hasEnquiries; - /** + * Eligibility criteria + *

* A description of any eligibility criteria for potential suppliers. - * */ @JsonProperty("eligibilityCriteria") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("A description of any eligibility criteria for potential suppliers.") + @ExcelExport private String eligibilityCriteria; - /** * Period *

- * - * */ @JsonProperty("awardPeriod") - private Period awardPeriod; - + @JsonPropertyDescription(" ") @ExcelExport - @JsonProperty("numberOfTenderers") - @Merge(MergeStrategy.ocdsVersion) - private Integer numberOfTenderers; - + private Period awardPeriod; /** - * All entities who submit a tender. - * + * Period + *

*/ + @JsonProperty("contractPeriod") + @JsonPropertyDescription(" ") @ExcelExport - @JsonProperty("tenderers") - @JsonDeserialize(as = java.util.LinkedHashSet.class) - @Merge(MergeStrategy.ocdsVersion) - private Set tenderers = new LinkedHashSet(); - + private Period contractPeriod; /** - * Organization + * Number of tenderers *

- * An organization. - * + * The number of parties who submit a bid. */ + @JsonProperty("numberOfTenderers") @ExcelExport - @JsonProperty("procuringEntity") - private Organization procuringEntity; - + @JsonPropertyDescription("The number of parties who submit a bid.") + private Integer numberOfTenderers; /** - * All documents and attachments related to the tender, including any notices. See the - * [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type) - * for details of potential documents to include. - * + * Tenderers + *

+ * All parties who submit a bid on a tender. More detailed information on bids and the bidding organization can + * be provided using the optional bid extension. */ + @JsonProperty("tenderers") + @JsonDeserialize(as = LinkedHashSet.class) + @JsonPropertyDescription("All parties who submit a bid on a tender. More detailed information on bids and the " + + "bidding organization can be provided using the optional bid extension.") @ExcelExport + private Set tenderers = new LinkedHashSet(); + /** + * Documents + *

+ * All documents and attachments related to the tender, including any notices. See the [documentType codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#document-type) for details of potential + * documents to include. Common documents include official legal notices of tender, technical specifications, + * evaluation criteria, and, as a tender process progresses, clarifications and replies to queries. + */ @JsonProperty("documents") - @Merge(MergeStrategy.arrayMergeById) + @JsonPropertyDescription("All documents and attachments related to the tender, including any notices. See the " + + "[documentType codelist](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#document-type) for details of potential documents to include. Common " + + "documents include official legal notices of tender, technical specifications, evaluation criteria, " + + "and, as a tender process progresses, clarifications and replies to queries.") + @ExcelExport private List documents = new ArrayList(); - /** + * Milestones + *

* A list of milestones associated with the tender. - * */ @JsonProperty("milestones") - @Merge(MergeStrategy.arrayMergeById) + @JsonPropertyDescription("A list of milestones associated with the tender.") private List milestones = new ArrayList(); - /** - * Amendment information + * Amendments *

- * - * + * A tender amendment is a formal change to the tender, and generally involves the publication of a new tender + * notice/release. The rationale and a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + @JsonPropertyDescription("A tender amendment is a formal change to the tender, and generally involves the " + + "publication of a new tender notice/release. The rationale and a description of the changes made can be" + + " provided here.") + private List amendments = new ArrayList(); + /** + * Amendment + *

+ * Amendment information */ @JsonProperty("amendment") + @JsonPropertyDescription("Amendment information") private Amendment amendment; /** * Tender ID *

- * An identifier for this tender process. This may be the same as the ocid, or may be drawn from an - * internally held identifier for this tender. + * An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally + * held identifier for this tender. * (Required) - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -304,23 +381,20 @@ public String getId() { /** * Tender ID *

- * An identifier for this tender process. This may be the same as the ocid, or may be drawn from an - * internally held identifier for this tender. + * An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally + * held identifier for this tender. * (Required) - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** * Tender title - * - * @return - * The title + *

+ * A title for this tender. This will often be used by applications as a headline to attract interest, and to + * help analysts understand the nature of this procurement. */ @JsonProperty("title") public String getTitle() { @@ -329,20 +403,20 @@ public String getTitle() { /** * Tender title - * - * @param title - * The title + *

+ * A title for this tender. This will often be used by applications as a headline to attract interest, and to + * help analysts understand the nature of this procurement. */ @JsonProperty("title") - public void setTitle(final String title) { + public void setTitle(String title) { this.title = title; } /** * Tender description - * - * @return - * The description + *

+ * A summary description of the tender. This should complement structured information provided using the items + * array. Descriptions should be short and easy to read. Avoid using ALL CAPS. */ @JsonProperty("description") public String getDescription() { @@ -351,23 +425,20 @@ public String getDescription() { /** * Tender description - * - * @param description - * The description + *

+ * A summary description of the tender. This should complement structured information provided using the items + * array. Descriptions should be short and easy to read. Avoid using ALL CAPS. */ @JsonProperty("description") - public void setDescription(final String description) { + public void setDescription(String description) { this.description = description; } /** - * Tender Status + * Tender status *

- * The current status of the tender based on the - * [tenderStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#tender-status) - * - * @return - * The status + * The current status of the tender based on the [tenderStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#tender-status) */ @JsonProperty("status") public Status getStatus() { @@ -375,27 +446,41 @@ public Status getStatus() { } /** - * Tender Status + * Tender status *

- * The current status of the tender based on the - * [tenderStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#tender-status) - * - * @param status - * The status + * The current status of the tender based on the [tenderStatus codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#tender-status) */ @JsonProperty("status") - public void setStatus(final Status status) { + public void setStatus(Status status) { this.status = status; } + /** + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section + */ + @JsonProperty("procuringEntity") + public Organization getProcuringEntity() { + return procuringEntity; + } + + /** + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section + */ + @JsonProperty("procuringEntity") + public void setProcuringEntity(Organization procuringEntity) { + this.procuringEntity = procuringEntity; + } + /** * Items to be procured *

- * The goods and services to be purchased, broken into line items wherever possible. - * Items should not be duplicated, but a quantity of 2 specified instead. - * - * @return - * The items + * The goods and services to be purchased, broken into line items wherever possible. Items should not be + * duplicated, but a quantity of 2 specified instead. */ @JsonProperty("items") public Set getItems() { @@ -405,64 +490,56 @@ public Set getItems() { /** * Items to be procured *

- * The goods and services to be purchased, broken into line items wherever possible. - * Items should not be duplicated, but a quantity of 2 specified instead. - * - * @param items - * The items + * The goods and services to be purchased, broken into line items wherever possible. Items should not be + * duplicated, but a quantity of 2 specified instead. */ @JsonProperty("items") - public void setItems(final Set items) { + public void setItems(Set items) { this.items = items; } /** - * - * @return - * The minValue + * Value + *

*/ - @JsonProperty("minValue") - public Amount getMinValue() { - return minValue; + @JsonProperty("value") + public Amount getValue() { + return value; } /** - * - * @param minValue - * The minValue + * Value + *

*/ - @JsonProperty("minValue") - public void setMinValue(final Amount minValue) { - this.minValue = minValue; + @JsonProperty("value") + public void setValue(Amount value) { + this.value = value; } /** - * - * @return - * The value + * Value + *

*/ - @JsonProperty("value") - public Amount getValue() { - return value; + @JsonProperty("minValue") + public Amount getMinValue() { + return minValue; } /** - * - * @param value - * The value + * Value + *

*/ - @JsonProperty("value") - public void setValue(final Amount value) { - this.value = value; + @JsonProperty("minValue") + public void setMinValue(Amount minValue) { + this.minValue = minValue; } /** - * Specify tendering method against the - * [method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#method) as per - * [GPA definitions](http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm) of Open, Selective, Limited - * - * @return - * The procurementMethod + * Procurement method + *

+ * Specify tendering method using the [method codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#method). This is a closed codelist. Local method types should be mapped to + * this list. */ @JsonProperty("procurementMethod") public ProcurementMethod getProcurementMethod() { @@ -470,23 +547,44 @@ public ProcurementMethod getProcurementMethod() { } /** - * Specify tendering method against the - * [method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#method) as per - * [GPA definitions](http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm) of Open, Selective, Limited - * - * @param procurementMethod - * The procurementMethod + * Procurement method + *

+ * Specify tendering method using the [method codelist](http://standard.open-contracting + * .org/latest/en/schema/codelists/#method). This is a closed codelist. Local method types should be mapped to + * this list. */ @JsonProperty("procurementMethod") - public void setProcurementMethod(final ProcurementMethod procurementMethod) { + public void setProcurementMethod(ProcurementMethod procurementMethod) { this.procurementMethod = procurementMethod; } /** - * Rationale of procurement method, especially in the case of Limited tendering. - * - * @return - * The procurementMethodRationale + * Procurement method details + *

+ * Additional detail on the procurement method used. This field may be used to provide the local name of the + * particular procurement method used. + */ + @JsonProperty("procurementMethodDetails") + public String getProcurementMethodDetails() { + return procurementMethodDetails; + } + + /** + * Procurement method details + *

+ * Additional detail on the procurement method used. This field may be used to provide the local name of the + * particular procurement method used. + */ + @JsonProperty("procurementMethodDetails") + public void setProcurementMethodDetails(String procurementMethodDetails) { + this.procurementMethodDetails = procurementMethodDetails; + } + + /** + * Procurement method rationale + *

+ * Rationale for the chosen procurement method. This is especially important to provide a justification in the + * case of limited tenders or direct awards. */ @JsonProperty("procurementMethodRationale") public String getProcurementMethodRationale() { @@ -494,22 +592,71 @@ public String getProcurementMethodRationale() { } /** - * Rationale of procurement method, especially in the case of Limited tendering. - * - * @param procurementMethodRationale - * The procurementMethodRationale + * Procurement method rationale + *

+ * Rationale for the chosen procurement method. This is especially important to provide a justification in the + * case of limited tenders or direct awards. */ @JsonProperty("procurementMethodRationale") - public void setProcurementMethodRationale(final String procurementMethodRationale) { + public void setProcurementMethodRationale(String procurementMethodRationale) { this.procurementMethodRationale = procurementMethodRationale; } /** - * Specify the award criteria for the procurement, using the - * [award criteria codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#award-criteria) - * - * @return - * The awardCriteria + * Main procurement category + *

+ * The primary category describing the main object of this contracting process from the [procurementCategory] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#procurement-category) codelist. This is a + * closed codelist. Local classifications should be mapped to this list. + */ + @JsonProperty("mainProcurementCategory") + public MainProcurementCategory getMainProcurementCategory() { + return mainProcurementCategory; + } + + /** + * Main procurement category + *

+ * The primary category describing the main object of this contracting process from the [procurementCategory] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#procurement-category) codelist. This is a + * closed codelist. Local classifications should be mapped to this list. + */ + @JsonProperty("mainProcurementCategory") + public void setMainProcurementCategory(MainProcurementCategory mainProcurementCategory) { + this.mainProcurementCategory = mainProcurementCategory; + } + + /** + * Additional procurement categories + *

+ * Any additional categories which describe the objects of this contracting process, from the + * [extendedProcurementCategory](http://standard.open-contracting + * .org/latest/en/schema/codelists/#extended-procurement-category) codelist. This is an open codelist. Local + * categories can be included in this list. + */ + @JsonProperty("additionalProcurementCategories") + public List getAdditionalProcurementCategories() { + return additionalProcurementCategories; + } + + /** + * Additional procurement categories + *

+ * Any additional categories which describe the objects of this contracting process, from the + * [extendedProcurementCategory](http://standard.open-contracting + * .org/latest/en/schema/codelists/#extended-procurement-category) codelist. This is an open codelist. Local + * categories can be included in this list. + */ + @JsonProperty("additionalProcurementCategories") + public void setAdditionalProcurementCategories(List additionalProcurementCategories) { + this.additionalProcurementCategories = additionalProcurementCategories; + } + + /** + * Award criteria + *

+ * Specify the award criteria for the procurement, using the [award criteria codelist](http://standard + * .open-contracting.org/latest/en/schema/codelists/#award-criteria) */ @JsonProperty("awardCriteria") public String getAwardCriteria() { @@ -517,22 +664,20 @@ public String getAwardCriteria() { } /** - * Specify the award criteria for the procurement, using the - * [award criteria codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#award-criteria) - * - * @param awardCriteria - * The awardCriteria + * Award criteria + *

+ * Specify the award criteria for the procurement, using the [award criteria codelist](http://standard + * .open-contracting.org/latest/en/schema/codelists/#award-criteria) */ @JsonProperty("awardCriteria") - public void setAwardCriteria(final String awardCriteria) { + public void setAwardCriteria(String awardCriteria) { this.awardCriteria = awardCriteria; } /** + * Award criteria details + *

* Any detailed or further information on the award or selection criteria. - * - * @return - * The awardCriteriaDetails */ @JsonProperty("awardCriteriaDetails") public String getAwardCriteriaDetails() { @@ -540,51 +685,42 @@ public String getAwardCriteriaDetails() { } /** + * Award criteria details + *

* Any detailed or further information on the award or selection criteria. - * - * @param awardCriteriaDetails - * The awardCriteriaDetails */ @JsonProperty("awardCriteriaDetails") - public void setAwardCriteriaDetails(final String awardCriteriaDetails) { + public void setAwardCriteriaDetails(String awardCriteriaDetails) { this.awardCriteriaDetails = awardCriteriaDetails; } /** - * Specify the method by which bids must be submitted, in person, written, or electronic auction. - * Using the - * [submission method codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#submission-method) - * - * @return - * The submissionMethod + * Submission method + *

+ * Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the + * [submission method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#submission-method) */ @JsonProperty("submissionMethod") - public Set getSubmissionMethod() { + public List getSubmissionMethod() { return submissionMethod; } /** - * Specify the method by which bids must be submitted, in person, written, or electronic auction. - * Using the - * [submission method codelist] - * (http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#submission-method) - * - * @param submissionMethod - * The submissionMethod + * Submission method + *

+ * Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the + * [submission method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#submission-method) */ @JsonProperty("submissionMethod") - public void setSubmissionMethod(final Set submissionMethod) { + public void setSubmissionMethod(List submissionMethod) { this.submissionMethod = submissionMethod; } /** - * Any detailed or further information on the submission method. This may include the address, - * e-mail address or online service to which bids should be submitted, - * and any special requirements to be followed for submissions. - * - * @return - * The submissionMethodDetails + * Submission method details + *

+ * Any detailed or further information on the submission method. This may include the address, e-mail address or + * online service to which bids should be submitted, and any special requirements to be followed for submissions. */ @JsonProperty("submissionMethodDetails") public String getSubmissionMethodDetails() { @@ -592,25 +728,19 @@ public String getSubmissionMethodDetails() { } /** - * Any detailed or further information on the submission method. This may include the address, - * e-mail address or online service to which bids should be submitted, - * and any special requirements to be followed for submissions. - * - * @param submissionMethodDetails - * The submissionMethodDetails + * Submission method details + *

+ * Any detailed or further information on the submission method. This may include the address, e-mail address or + * online service to which bids should be submitted, and any special requirements to be followed for submissions. */ @JsonProperty("submissionMethodDetails") - public void setSubmissionMethodDetails(final String submissionMethodDetails) { + public void setSubmissionMethodDetails(String submissionMethodDetails) { this.submissionMethodDetails = submissionMethodDetails; } /** * Period *

- * - * - * @return - * The tenderPeriod */ @JsonProperty("tenderPeriod") public Period getTenderPeriod() { @@ -620,23 +750,15 @@ public Period getTenderPeriod() { /** * Period *

- * - * - * @param tenderPeriod - * The tenderPeriod */ @JsonProperty("tenderPeriod") - public void setTenderPeriod(final Period tenderPeriod) { + public void setTenderPeriod(Period tenderPeriod) { this.tenderPeriod = tenderPeriod; } /** * Period *

- * - * - * @return - * The enquiryPeriod */ @JsonProperty("enquiryPeriod") public Period getEnquiryPeriod() { @@ -646,21 +768,18 @@ public Period getEnquiryPeriod() { /** * Period *

- * - * - * @param enquiryPeriod - * The enquiryPeriod */ @JsonProperty("enquiryPeriod") - public void setEnquiryPeriod(final Period enquiryPeriod) { + public void setEnquiryPeriod(Period enquiryPeriod) { this.enquiryPeriod = enquiryPeriod; } /** - * A Yes/No field to indicate whether enquiries were part of tender process. - * - * @return - * The hasEnquiries + * Has enquiries? + *

+ * A true/false field to indicate whether any enquiries were received during the tender process. Structured + * information on enquiries that were received, and responses to them, can be provided using the enquiries + * extension. */ @JsonProperty("hasEnquiries") public Boolean getHasEnquiries() { @@ -668,21 +787,21 @@ public Boolean getHasEnquiries() { } /** - * A Yes/No field to indicate whether enquiries were part of tender process. - * - * @param hasEnquiries - * The hasEnquiries + * Has enquiries? + *

+ * A true/false field to indicate whether any enquiries were received during the tender process. Structured + * information on enquiries that were received, and responses to them, can be provided using the enquiries + * extension. */ @JsonProperty("hasEnquiries") - public void setHasEnquiries(final Boolean hasEnquiries) { + public void setHasEnquiries(Boolean hasEnquiries) { this.hasEnquiries = hasEnquiries; } /** + * Eligibility criteria + *

* A description of any eligibility criteria for potential suppliers. - * - * @return - * The eligibilityCriteria */ @JsonProperty("eligibilityCriteria") public String getEligibilityCriteria() { @@ -690,23 +809,18 @@ public String getEligibilityCriteria() { } /** + * Eligibility criteria + *

* A description of any eligibility criteria for potential suppliers. - * - * @param eligibilityCriteria - * The eligibilityCriteria */ @JsonProperty("eligibilityCriteria") - public void setEligibilityCriteria(final String eligibilityCriteria) { + public void setEligibilityCriteria(String eligibilityCriteria) { this.eligibilityCriteria = eligibilityCriteria; } /** * Period *

- * - * - * @return - * The awardPeriod */ @JsonProperty("awardPeriod") public Period getAwardPeriod() { @@ -716,20 +830,34 @@ public Period getAwardPeriod() { /** * Period *

- * - * - * @param awardPeriod - * The awardPeriod */ @JsonProperty("awardPeriod") - public void setAwardPeriod(final Period awardPeriod) { + public void setAwardPeriod(Period awardPeriod) { this.awardPeriod = awardPeriod; } /** - * - * @return - * The numberOfTenderers + * Period + *

+ */ + @JsonProperty("contractPeriod") + public Period getContractPeriod() { + return contractPeriod; + } + + /** + * Period + *

+ */ + @JsonProperty("contractPeriod") + public void setContractPeriod(Period contractPeriod) { + this.contractPeriod = contractPeriod; + } + + /** + * Number of tenderers + *

+ * The number of parties who submit a bid. */ @JsonProperty("numberOfTenderers") public Integer getNumberOfTenderers() { @@ -737,20 +865,20 @@ public Integer getNumberOfTenderers() { } /** - * - * @param numberOfTenderers - * The numberOfTenderers + * Number of tenderers + *

+ * The number of parties who submit a bid. */ @JsonProperty("numberOfTenderers") - public void setNumberOfTenderers(final Integer numberOfTenderers) { + public void setNumberOfTenderers(Integer numberOfTenderers) { this.numberOfTenderers = numberOfTenderers; } /** - * All entities who submit a tender. - * - * @return - * The tenderers + * Tenderers + *

+ * All parties who submit a bid on a tender. More detailed information on bids and the bidding organization can + * be provided using the optional bid extension. */ @JsonProperty("tenderers") public Set getTenderers() { @@ -758,49 +886,23 @@ public Set getTenderers() { } /** - * All entities who submit a tender. - * - * @param tenderers - * The tenderers + * Tenderers + *

+ * All parties who submit a bid on a tender. More detailed information on bids and the bidding organization can + * be provided using the optional bid extension. */ @JsonProperty("tenderers") - public void setTenderers(final Set tenderers) { + public void setTenderers(Set tenderers) { this.tenderers = tenderers; } /** - * Organization - *

- * An organization. - * - * @return - * The procuringEntity - */ - @JsonProperty("procuringEntity") - public Organization getProcuringEntity() { - return procuringEntity; - } - - /** - * Organization + * Documents *

- * An organization. - * - * @param procuringEntity - * The procuringEntity - */ - @JsonProperty("procuringEntity") - public void setProcuringEntity(final Organization procuringEntity) { - this.procuringEntity = procuringEntity; - } - - /** - * All documents and attachments related to the tender, including any notices. See the - * [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type) - * for details of potential documents to include. - * - * @return - * The documents + * All documents and attachments related to the tender, including any notices. See the [documentType codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#document-type) for details of potential + * documents to include. Common documents include official legal notices of tender, technical specifications, + * evaluation criteria, and, as a tender process progresses, clarifications and replies to queries. */ @JsonProperty("documents") public List getDocuments() { @@ -808,23 +910,22 @@ public List getDocuments() { } /** - * All documents and attachments related to the tender, including any notices. See the - * [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type) - * for details of potential documents to include. - * - * @param documents - * The documents + * Documents + *

+ * All documents and attachments related to the tender, including any notices. See the [documentType codelist] + * (http://standard.open-contracting.org/latest/en/schema/codelists/#document-type) for details of potential + * documents to include. Common documents include official legal notices of tender, technical specifications, + * evaluation criteria, and, as a tender process progresses, clarifications and replies to queries. */ @JsonProperty("documents") - public void setDocuments(final List documents) { + public void setDocuments(List documents) { this.documents = documents; } /** + * Milestones + *

* A list of milestones associated with the tender. - * - * @return - * The milestones */ @JsonProperty("milestones") public List getMilestones() { @@ -832,23 +933,41 @@ public List getMilestones() { } /** + * Milestones + *

* A list of milestones associated with the tender. - * - * @param milestones - * The milestones */ @JsonProperty("milestones") - public void setMilestones(final List milestones) { + public void setMilestones(List milestones) { this.milestones = milestones; } /** - * Amendment information + * Amendments *

- * - * - * @return - * The amendment + * A tender amendment is a formal change to the tender, and generally involves the publication of a new tender + * notice/release. The rationale and a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + public List getAmendments() { + return amendments; + } + + /** + * Amendments + *

+ * A tender amendment is a formal change to the tender, and generally involves the publication of a new tender + * notice/release. The rationale and a description of the changes made can be provided here. + */ + @JsonProperty("amendments") + public void setAmendments(List amendments) { + this.amendments = amendments; + } + + /** + * Amendment + *

+ * Amendment information */ @JsonProperty("amendment") public Amendment getAmendment() { @@ -856,54 +975,85 @@ public Amendment getAmendment() { } /** - * Amendment information + * Amendment *

- * - * - * @param amendment - * The amendment + * Amendment information */ @JsonProperty("amendment") - public void setAmendment(final Amendment amendment) { + public void setAmendment(Amendment amendment) { this.amendment = amendment; } @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("id", id) + .append("title", title) + .append("description", description) + .append("status", status) + .append("procuringEntity", procuringEntity) + .append("items", items) + .append("value", value) + .append("minValue", minValue) + .append("procurementMethod", procurementMethod) + .append("procurementMethodDetails", procurementMethodDetails) + .append("procurementMethodRationale", procurementMethodRationale) + .append("mainProcurementCategory", mainProcurementCategory) + .append("additionalProcurementCategories", additionalProcurementCategories) + .append("awardCriteria", awardCriteria) + .append("awardCriteriaDetails", awardCriteriaDetails) + .append("submissionMethod", submissionMethod) + .append("submissionMethodDetails", submissionMethodDetails) + .append("tenderPeriod", tenderPeriod) + .append("enquiryPeriod", enquiryPeriod) + .append("hasEnquiries", hasEnquiries) + .append("eligibilityCriteria", eligibilityCriteria) + .append("awardPeriod", awardPeriod) + .append("contractPeriod", contractPeriod) + .append("numberOfTenderers", numberOfTenderers) + .append("tenderers", tenderers) + .append("documents", documents) + .append("milestones", milestones) + .append("amendments", amendments) + .append("amendment", amendment) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(id). - append(title). - append(description). - append(status). - append(items). - append(minValue). - append(value). - append(procurementMethod). - append(procurementMethodRationale). - append(awardCriteria). - append(awardCriteriaDetails). - append(submissionMethod). - append(submissionMethodDetails). - append(tenderPeriod). - append(enquiryPeriod). - append(hasEnquiries). - append(eligibilityCriteria). - append(awardPeriod). - append(numberOfTenderers). - append(tenderers). - append(procuringEntity). - append(documents). - append(milestones).append(amendment). - toHashCode(); + return new HashCodeBuilder().append(amendment) + .append(documents) + .append(awardPeriod) + .append(description) + .append(amendments) + .append(mainProcurementCategory) + .append(title) + .append(procurementMethodDetails) + .append(additionalProcurementCategories) + .append(minValue) + .append(procurementMethod) + .append(enquiryPeriod) + .append(awardCriteria) + .append(eligibilityCriteria) + .append(id) + .append(value) + .append(tenderPeriod) + .append(procurementMethodRationale) + .append(procuringEntity) + .append(submissionMethod) + .append(hasEnquiries) + .append(contractPeriod) + .append(numberOfTenderers) + .append(submissionMethodDetails) + .append(awardCriteriaDetails) + .append(milestones) + .append(items) + .append(tenderers) + .append(status) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -911,64 +1061,70 @@ public boolean equals(final Object other) { return false; } Tender rhs = ((Tender) other); - return new EqualsBuilder(). - append(id, rhs.id). - append(title, rhs.title). - append(description, rhs.description). - append(status, rhs.status). - append(items, rhs.items). - append(minValue, rhs.minValue). - append(value, rhs.value). - append(procurementMethod, rhs.procurementMethod). - append(procurementMethodRationale, rhs.procurementMethodRationale). - append(awardCriteria, rhs.awardCriteria). - append(awardCriteriaDetails, rhs.awardCriteriaDetails). - append(submissionMethod, rhs.submissionMethod). - append(submissionMethodDetails, rhs.submissionMethodDetails). - append(tenderPeriod, rhs.tenderPeriod). - append(enquiryPeriod, rhs.enquiryPeriod). - append(hasEnquiries, rhs.hasEnquiries). - append(eligibilityCriteria, rhs.eligibilityCriteria). - append(awardPeriod, rhs.awardPeriod). - append(numberOfTenderers, rhs.numberOfTenderers). - append(tenderers, rhs.tenderers). - append(procuringEntity, rhs.procuringEntity). - append(documents, rhs.documents). - append(milestones, rhs.milestones). - append(amendment, rhs.amendment). - isEquals(); + return new EqualsBuilder().append(amendment, rhs.amendment) + .append(documents, rhs.documents) + .append(awardPeriod, rhs.awardPeriod) + .append(description, rhs.description) + .append(amendments, rhs.amendments) + .append(mainProcurementCategory, rhs.mainProcurementCategory) + .append(title, rhs.title) + .append(procurementMethodDetails, rhs.procurementMethodDetails) + .append(additionalProcurementCategories, rhs.additionalProcurementCategories) + .append(minValue, rhs.minValue) + .append(procurementMethod, rhs.procurementMethod) + .append(enquiryPeriod, rhs.enquiryPeriod) + .append(awardCriteria, rhs.awardCriteria) + .append(eligibilityCriteria, rhs.eligibilityCriteria) + .append(id, rhs.id) + .append(value, rhs.value) + .append(tenderPeriod, rhs.tenderPeriod) + .append(procurementMethodRationale, rhs.procurementMethodRationale) + .append(procuringEntity, rhs.procuringEntity) + .append(submissionMethod, rhs.submissionMethod) + .append(hasEnquiries, rhs.hasEnquiries) + .append(contractPeriod, rhs.contractPeriod) + .append(numberOfTenderers, rhs.numberOfTenderers) + .append(submissionMethodDetails, rhs.submissionMethodDetails) + .append(awardCriteriaDetails, rhs.awardCriteriaDetails) + .append(milestones, rhs.milestones) + .append(items, rhs.items) + .append(tenderers, rhs.tenderers) + .append(status, rhs.status) + .isEquals(); } - public enum ProcurementMethod { - open("open"), - - selective("selective"), - - limited("limited"); + public enum MainProcurementCategory { + GOODS("goods"), + WORKS("works"), + SERVICES("services"); private final String value; - - private static final Map CONSTANTS = new HashMap(); + private static final Map CONSTANTS = new HashMap(); static { - for (ProcurementMethod c: values()) { + for (MainProcurementCategory c : values()) { CONSTANTS.put(c.value, c); } } - ProcurementMethod(final String value) { + MainProcurementCategory(String value) { this.value = value; } - @JsonValue @Override public String toString() { return this.value; } + @JsonValue + public String value() { + return this.value; + } + @JsonCreator - public static ProcurementMethod fromValue(final String value) { - ProcurementMethod constant = CONSTANTS.get(value); + public static MainProcurementCategory fromValue(String value) { + MainProcurementCategory constant = CONSTANTS.get(value); if (constant == null) { throw new IllegalArgumentException(value); } else { @@ -978,15 +1134,10 @@ public static ProcurementMethod fromValue(final String value) { } - - public enum SubmissionMethod { electronicAuction("electronicAuction"), - electronicSubmission("electronicSubmission"), - written("written"), - inPerson("inPerson"); private final String value; @@ -994,7 +1145,7 @@ public enum SubmissionMethod { private static final Map CONSTANTS = new HashMap(); static { - for (SubmissionMethod c: values()) { + for (SubmissionMethod c : values()) { CONSTANTS.put(c.value, c); } } @@ -1008,10 +1159,41 @@ public enum SubmissionMethod { public String toString() { return this.value; } + } + + + public enum ProcurementMethod { + + open("open"), + selective("selective"), + limited("limited"), + direct("direct"); + private final String value; + private static final Map CONSTANTS = new HashMap(); + + static { + for (ProcurementMethod c : values()) { + CONSTANTS.put(c.value, c); + } + } + + ProcurementMethod(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } @JsonCreator - public static SubmissionMethod fromValue(final String value) { - SubmissionMethod constant = CONSTANTS.get(value); + public static ProcurementMethod fromValue(String value) { + ProcurementMethod constant = CONSTANTS.get(value); if (constant == null) { throw new IllegalArgumentException(value); } else { @@ -1022,38 +1204,39 @@ public static SubmissionMethod fromValue(final String value) { } public enum Status { - planned("planned"), + planning("planning"), + planned("planned"), active("active"), - cancelled("cancelled"), - unsuccessful("unsuccessful"), - - complete("complete"); - + complete("complete"), + withdrawn("withdrawn"); private final String value; - private static final Map CONSTANTS = new HashMap(); static { - for (Status c: values()) { + for (Status c : values()) { CONSTANTS.put(c.value, c); } } - Status(final String value) { + Status(String value) { this.value = value; } - @JsonValue @Override public String toString() { return this.value; } + @JsonValue + public String value() { + return this.value; + } + @JsonCreator - public static Status fromValue(final String value) { + public static Status fromValue(String value) { Status constant = CONSTANTS.get(value); if (constant == null) { throw new IllegalArgumentException(value); @@ -1064,8 +1247,4 @@ public static Status fromValue(final String value) { } - @Override - public Serializable getIdProperty() { - return id; - } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Transaction.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Transaction.java index 9d97851e6..57ea1f7a2 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Transaction.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Transaction.java @@ -1,104 +1,136 @@ package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import java.io.Serializable; +import java.net.URI; import java.util.Date; /** - * Transaction Information + * Transaction information *

- * A spending transaction related to the contracting process. Draws upon the data models of the - * [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md) and the - * [International Aid Transpareny Initiative] - * (http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) - * and should be used to cross-reference to more detailed information held using a Budget Data Package, IATI file, - * or to provide enough information to allow a user to manually or automatically cross-reference with - * some other published source of transactional spending data. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#transaction - * + * A spending transaction related to the contracting process. Draws upon the data models of the [Fiscal Data Package] + * (http://fiscal.dataprotocols.org/) and the [International Aid Transparency Initiative](http://iatistandard + * .org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more + * detailed information held using a Fiscal Data Package, IATI file, or to provide enough information to allow a user + * to manually or automatically cross-reference with some other published source of transactional spending data. */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "source", "date", + "value", + "payer", + "payee", + "uri", "amount", "providerOrganization", - "receiverOrganization", - "uri" + "receiverOrganization" }) -public class Transaction implements Identifiable { +public class Transaction { /** + * ID + *

* A unique identifier for this transaction. This identifier should be possible to cross-reference against the - * provided data source. For the budget data package this is the id, for IATI, the transaction reference. + * provided data source. For IATI this is the transaction reference. * (Required) - * */ @JsonProperty("id") - @Merge(MergeStrategy.overwrite) + @JsonPropertyDescription("A unique identifier for this transaction. This identifier should be possible to " + + "cross-reference against the provided data source. For IATI this is the transaction reference.") private String id; - /** - * Data Source + * Data source *

- * Used to point either to a corresponding Budget Data Package, IATI file, or machine or - * human-readable source where users can find further information on the budget line item identifiers, - * or project identifiers, provided here. - * + * Used to point either to a corresponding Fiscal Data Package, IATI file, or machine or human-readable source + * where users can find further information on the budget line item identifiers, or project identifiers, provided + * here. */ - @ExcelExport @JsonProperty("source") - @Merge(MergeStrategy.ocdsVersion) - private String source; - + @ExcelExport + @JsonPropertyDescription("Used to point either to a corresponding Fiscal Data Package, IATI file, or machine or " + + "human-readable source where users can find further information on the budget line item identifiers, or" + + " project identifiers, provided here.") + private URI source; /** + * Date + *

* The date of the transaction - * */ - @ExcelExport @JsonProperty("date") - @Merge(MergeStrategy.ocdsVersion) + @ExcelExport + @JsonPropertyDescription("The date of the transaction") private Date date; - + /** + * Value + *

+ */ + @JsonProperty("value") @ExcelExport + private Amount value; + /** + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section + */ + @JsonProperty("payer") + @JsonPropertyDescription("The id and name of the party being referenced. Used to cross-reference to the parties " + + "section") + private Organization payer; + /** + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section + */ + @JsonProperty("payee") + @JsonPropertyDescription("The id and name of the party being referenced. Used to cross-reference to the parties " + + "section") + private Organization payee; + /** + * Linked spending information + *

+ * A URI pointing directly to a machine-readable record about this spending transaction. + */ + @JsonProperty("uri") + @JsonPropertyDescription("A URI pointing directly to a machine-readable record about this spending transaction.") + private URI uri; + /** + * Value + *

+ */ @JsonProperty("amount") - private Amount amount; - @ExcelExport + private Amount amount; + /** + * Identifier + *

+ */ @JsonProperty("providerOrganization") - private Identifier providerOrganization; - @ExcelExport - @JsonProperty("receiverOrganization") - private Identifier receiverOrganization; - + private Identifier providerOrganization; /** - * Linked spending information + * Identifier *

- * A URI pointing directly to a machine-readable record about this spending transaction. - * */ - @JsonProperty("uri") - @Merge(MergeStrategy.ocdsVersion) - private String uri; + @JsonProperty("receiverOrganization") + @ExcelExport + private Identifier receiverOrganization; /** + * ID + *

* A unique identifier for this transaction. This identifier should be possible to cross-reference against the - * provided data source. For the budget data package this is the id, for IATI, the transaction reference. + * provided data source. For IATI this is the transaction reference. * (Required) - * - * @return - * The id */ @JsonProperty("id") public String getId() { @@ -106,53 +138,45 @@ public String getId() { } /** + * ID + *

* A unique identifier for this transaction. This identifier should be possible to cross-reference against the - * provided data source. For the budget data package this is the id, for IATI, the transaction reference. + * provided data source. For IATI this is the transaction reference. * (Required) - * - * @param id - * The id */ @JsonProperty("id") - public void setId(final String id) { + public void setId(String id) { this.id = id; } /** - * Data Source + * Data source *

- * Used to point either to a corresponding Budget Data Package, IATI file, or machine or - * human-readable source where users can find further information on the budget line item identifiers, - * or project identifiers, provided here. - * - * @return - * The source + * Used to point either to a corresponding Fiscal Data Package, IATI file, or machine or human-readable source + * where users can find further information on the budget line item identifiers, or project identifiers, provided + * here. */ @JsonProperty("source") - public String getSource() { + public URI getSource() { return source; } /** - * Data Source + * Data source *

- * Used to point either to a corresponding Budget Data Package, IATI file, or machine or - * human-readable source where users can find further information on the budget line item identifiers, - * or project identifiers, provided here. - * - * @param source - * The source + * Used to point either to a corresponding Fiscal Data Package, IATI file, or machine or human-readable source + * where users can find further information on the budget line item identifiers, or project identifiers, provided + * here. */ @JsonProperty("source") - public void setSource(final String source) { + public void setSource(URI source) { this.source = source; } /** + * Date + *

* The date of the transaction - * - * @return - * The date */ @JsonProperty("date") public Date getDate() { @@ -160,86 +184,80 @@ public Date getDate() { } /** + * Date + *

* The date of the transaction - * - * @param date - * The date */ @JsonProperty("date") - public void setDate(final Date date) { + public void setDate(Date date) { this.date = date; } /** - * - * @return - * The amount + * Value + *

*/ - @JsonProperty("amount") - public Amount getAmount() { - return amount; + @JsonProperty("value") + public Amount getValue() { + return value; } /** - * - * @param amount - * The amount + * Value + *

*/ - @JsonProperty("amount") - public void setAmount(final Amount amount) { - this.amount = amount; + @JsonProperty("value") + public void setValue(Amount value) { + this.value = value; } /** - * - * @return - * The providerOrganization + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section */ - @JsonProperty("providerOrganization") - public Identifier getProviderOrganization() { - return providerOrganization; + @JsonProperty("payer") + public Organization getPayer() { + return payer; } /** - * - * @param providerOrganization - * The providerOrganization + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section */ - @JsonProperty("providerOrganization") - public void setProviderOrganization(final Identifier providerOrganization) { - this.providerOrganization = providerOrganization; + @JsonProperty("payer") + public void setPayer(Organization payer) { + this.payer = payer; } /** - * - * @return - * The receiverOrganization + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section */ - @JsonProperty("receiverOrganization") - public Identifier getReceiverOrganization() { - return receiverOrganization; + @JsonProperty("payee") + public Organization getPayee() { + return payee; } /** - * - * @param receiverOrganization - * The receiverOrganization + * Organization reference + *

+ * The id and name of the party being referenced. Used to cross-reference to the parties section */ - @JsonProperty("receiverOrganization") - public void setReceiverOrganization(final Identifier receiverOrganization) { - this.receiverOrganization = receiverOrganization; + @JsonProperty("payee") + public void setPayee(Organization payee) { + this.payee = payee; } /** * Linked spending information *

* A URI pointing directly to a machine-readable record about this spending transaction. - * - * @return - * The uri */ @JsonProperty("uri") - public String getUri() { + public URI getUri() { return uri; } @@ -247,35 +265,98 @@ public String getUri() { * Linked spending information *

* A URI pointing directly to a machine-readable record about this spending transaction. - * - * @param uri - * The uri */ @JsonProperty("uri") - public void setUri(final String uri) { + public void setUri(URI uri) { this.uri = uri; } + /** + * Value + *

+ */ + @JsonProperty("amount") + public Amount getAmount() { + return amount; + } + + /** + * Value + *

+ */ + @JsonProperty("amount") + public void setAmount(Amount amount) { + this.amount = amount; + } + + /** + * Identifier + *

+ */ + @JsonProperty("providerOrganization") + public Identifier getProviderOrganization() { + return providerOrganization; + } + + /** + * Identifier + *

+ */ + @JsonProperty("providerOrganization") + public void setProviderOrganization(Identifier providerOrganization) { + this.providerOrganization = providerOrganization; + } + + /** + * Identifier + *

+ */ + @JsonProperty("receiverOrganization") + public Identifier getReceiverOrganization() { + return receiverOrganization; + } + + /** + * Identifier + *

+ */ + @JsonProperty("receiverOrganization") + public void setReceiverOrganization(Identifier receiverOrganization) { + this.receiverOrganization = receiverOrganization; + } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("id", id) + .append("source", source) + .append("date", date) + .append("value", value) + .append("payer", payer) + .append("payee", payee) + .append("uri", uri) + .append("amount", amount) + .append("providerOrganization", providerOrganization) + .append("receiverOrganization", receiverOrganization) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(id). - append(source). - append(date). - append(amount). - append(providerOrganization). - append(receiverOrganization). - append(uri). - toHashCode(); + return new HashCodeBuilder().append(date) + .append(payee) + .append(amount) + .append(providerOrganization) + .append(id) + .append(source) + .append(receiverOrganization) + .append(value) + .append(payer) + .append(uri) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -283,20 +364,17 @@ public boolean equals(final Object other) { return false; } Transaction rhs = ((Transaction) other); - return new EqualsBuilder(). - append(id, rhs.id). - append(source, rhs.source). - append(date, rhs.date). - append(amount, rhs.amount). - append(providerOrganization, rhs.providerOrganization). - append(receiverOrganization, rhs.receiverOrganization). - append(uri, rhs.uri). - isEquals(); - } - - @Override - public Serializable getIdProperty() { - return id; + return new EqualsBuilder().append(date, rhs.date) + .append(payee, rhs.payee) + .append(amount, rhs.amount) + .append(providerOrganization, rhs.providerOrganization) + .append(id, rhs.id) + .append(source, rhs.source) + .append(receiverOrganization, rhs.receiverOrganization) + .append(value, rhs.value) + .append(payer, rhs.payer) + .append(uri, rhs.uri) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Unit.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Unit.java index 9f9ea5e6c..4bc21064c 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Unit.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Unit.java @@ -1,43 +1,136 @@ - package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.devgateway.ocds.persistence.mongo.merge.Merge; -import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; + +import java.net.URI; /** - * Description of the unit which the good comes in e.g. hours, kilograms. - * Made up of a unit name, and the value of a single unit. - * - * http://standard.open-contracting.org/latest/en/schema/reference/#unit + * Unit + *

+ * A description of the unit in which the supplies, services or works are provided (e.g. hours, kilograms) and the + * unit-price. For comparability, an established list of units can be used. */ +@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ + "scheme", + "id", "name", - "value" + "value", + "uri" }) public class Unit { /** - * Name of the unit - * + * Scheme + *

+ * The list from which units of measure identifiers are taken. This should be an entry from the options available + * in the [unitClassificationScheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#unit-classification-scheme) codelist. Use of the scheme 'UNCEFACT' for the + * UN/CEFACT Recommendation 20 list of 'Codes for Units of Measure Used in International Trade' is recommended, + * although other options are available. + */ + @JsonProperty("scheme") + @JsonPropertyDescription("The list from which units of measure identifiers are taken. This should be an entry " + + "from the options available in the [unitClassificationScheme](http://standard.open-contracting" + + ".org/latest/en/schema/codelists/#unit-classification-scheme) codelist. Use of the scheme 'UNCEFACT' " + + "for the UN/CEFACT Recommendation 20 list of 'Codes for Units of Measure Used in International Trade' " + + "is recommended, although other options are available.") + private String scheme; + /** + * ID + *

+ * The identifier from the codelist referenced in the scheme property. Check the codelist for details of how to + * find and use identifiers from the scheme in use. + */ + @JsonProperty("id") + @JsonPropertyDescription("The identifier from the codelist referenced in the scheme property. Check the codelist " + + "for details of how to find and use identifiers from the scheme in use.") + private String id; + /** + * Name + *

+ * Name of the unit. */ @JsonProperty("name") - @Merge(MergeStrategy.ocdsVersion) + @JsonPropertyDescription("Name of the unit.") private String name; - + /** + * Value + *

+ */ @JsonProperty("value") private Amount value; + /** + * URI + *

+ * If the scheme used provide a machine-readable URI for this unit of measure, this can be given. + */ + @JsonProperty("uri") + @JsonPropertyDescription("If the scheme used provide a machine-readable URI for this unit of measure, this can be" + + " given.") + private URI uri; /** - * Name of the unit - * - * @return - * The name + * Scheme + *

+ * The list from which units of measure identifiers are taken. This should be an entry from the options available + * in the [unitClassificationScheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#unit-classification-scheme) codelist. Use of the scheme 'UNCEFACT' for the + * UN/CEFACT Recommendation 20 list of 'Codes for Units of Measure Used in International Trade' is recommended, + * although other options are available. + */ + @JsonProperty("scheme") + public String getScheme() { + return scheme; + } + + /** + * Scheme + *

+ * The list from which units of measure identifiers are taken. This should be an entry from the options available + * in the [unitClassificationScheme](http://standard.open-contracting + * .org/latest/en/schema/codelists/#unit-classification-scheme) codelist. Use of the scheme 'UNCEFACT' for the + * UN/CEFACT Recommendation 20 list of 'Codes for Units of Measure Used in International Trade' is recommended, + * although other options are available. + */ + @JsonProperty("scheme") + public void setScheme(String scheme) { + this.scheme = scheme; + } + + /** + * ID + *

+ * The identifier from the codelist referenced in the scheme property. Check the codelist for details of how to + * find and use identifiers from the scheme in use. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * ID + *

+ * The identifier from the codelist referenced in the scheme property. Check the codelist for details of how to + * find and use identifiers from the scheme in use. + */ + @JsonProperty("id") + public void setId(String id) { + this.id = id; + } + + /** + * Name + *

+ * Name of the unit. */ @JsonProperty("name") public String getName() { @@ -45,20 +138,18 @@ public String getName() { } /** - * Name of the unit - * - * @param name - * The name + * Name + *

+ * Name of the unit. */ @JsonProperty("name") - public void setName(final String name) { + public void setName(String name) { this.name = name; } /** - * - * @return - * The value + * Value + *

*/ @JsonProperty("value") public Amount getValue() { @@ -66,30 +157,56 @@ public Amount getValue() { } /** - * - * @param value - * The value + * Value + *

*/ @JsonProperty("value") - public void setValue(final Amount value) { + public void setValue(Amount value) { this.value = value; } + /** + * URI + *

+ * If the scheme used provide a machine-readable URI for this unit of measure, this can be given. + */ + @JsonProperty("uri") + public URI getUri() { + return uri; + } + + /** + * URI + *

+ * If the scheme used provide a machine-readable URI for this unit of measure, this can be given. + */ + @JsonProperty("uri") + public void setUri(URI uri) { + this.uri = uri; + } + @Override public String toString() { - return ToStringBuilder.reflectionToString(this); + return new ToStringBuilder(this).append("scheme", scheme) + .append("id", id) + .append("name", name) + .append("value", value) + .append("uri", uri) + .toString(); } @Override public int hashCode() { - return new HashCodeBuilder(). - append(name). - append(value). - toHashCode(); + return new HashCodeBuilder().append(scheme) + .append(name) + .append(id) + .append(value) + .append(uri) + .toHashCode(); } @Override - public boolean equals(final Object other) { + public boolean equals(Object other) { if (other == this) { return true; } @@ -97,10 +214,12 @@ public boolean equals(final Object other) { return false; } Unit rhs = ((Unit) other); - return new EqualsBuilder(). - append(name, rhs.name). - append(value, rhs.value). - isEquals(); + return new EqualsBuilder().append(scheme, rhs.scheme) + .append(name, rhs.name) + .append(id, rhs.id) + .append(value, rhs.value) + .append(uri, rhs.uri) + .isEquals(); } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/constants/MongoConstants.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/constants/MongoConstants.java index 37c214c0c..e440c4d8a 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/constants/MongoConstants.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/constants/MongoConstants.java @@ -21,10 +21,68 @@ private MongoConstants() { public static final int IMPORT_ROW_BATCH = 1000; + public static final String MONGO_LANGUAGE = "english"; + public static final class FieldNames { + public static final String BUYER_ID = "buyer._id"; + public static final String BUYER_NAME = "buyer.name"; + public static final String AWARDS_DATE = "awards.date"; + public static final String AWARDS_STATUS = "awards.status"; + public static final String AWARDS_SUPPLIERS_ID = "awards.suppliers._id"; + public static final String AWARDS_SUPPLIERS_NAME = "awards.suppliers.name"; + public static final String AWARDS_VALUE_AMOUNT = "awards.value.amount"; + public static final String AWARDS_VALUE = "awards.value"; public static final String TENDER_PERIOD_START_DATE = "tender.tenderPeriod.startDate"; + public static final String TENDER_PROCURING_ENTITY_ID = "tender.procuringEntity._id"; + public static final String TENDER_PROCURING_ENTITY_NAME = "tender.procuringEntity.name"; + public static final String TENDER_PERIOD = "tender.tenderPeriod"; + public static final String TENDER_TITLE = "tender.title"; public static final String TENDER_PERIOD_END_DATE = "tender.tenderPeriod.endDate"; - public static final String TENDER_PERIOD_START_DATE_REF = "$" + TENDER_PERIOD_START_DATE; - public static final String TENDER_PERIOD_END_DATE_REF = "$" + TENDER_PERIOD_END_DATE; + public static final String TENDER_VALUE = "tender.value"; + public static final String TENDER_VALUE_AMOUNT = "tender.value.amount"; + public static final String TENDER_NO_TENDERERS = "tender.numberOfTenderers"; + public static final String TENDER_PROC_METHOD = "tender.procurementMethod"; + public static final String TENDER_STATUS = "tender.status"; + public static final String TENDER_SUBMISSION_METHOD = "tender.submissionMethod"; + public static final String BIDS_DETAILS_TENDERERS_ID = "bids.details.tenderers._id"; + public static final String BIDS_DETAILS_VALUE_AMOUNT = "bids.details.value.amount"; + public static final String FLAGS_TOTAL_FLAGGED = "flags.totalFlagged"; + public static final String FLAGS_COUNT = "flags.flaggedStats.count"; + } + + public static final class Filters { + public static final String YEAR = "year"; + + public static final String TEXT = "text"; + + public static final String AWARD_STATUS = "awardStatus"; + + public static final String BID_TYPE_ID = "bidTypeId"; + + public static final String NOT_BID_TYPE_ID = "notBidTypeId"; + + public static final String PROCURING_ENTITY_ID = "procuringEntityId"; + + public static final String NOT_PROCURING_ENTITY_ID = "notProcuringEntityId"; + + public static final String SUPPLIER_ID = "supplierId"; + + public static final String BIDDER_ID = "bidderId"; + + public static final String TENDER_LOC = "tenderLoc"; + + public static final String PROCUREMENT_METHOD = "procurementMethod"; + + public static final String TENDER_VALUE = "tenderValue"; + + public static final String AWARD_VALUE = "awardValue"; + + public static final String FLAG_TYPE = "flagType"; + + public static final String ELECTRONIC_SUBMISSION = "electronicSubmission"; + + public static final String FLAGGED = "flagged"; + + public static final String TOTAL_FLAGGED = "totalFlagged"; } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/AbstractExcelSheet.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/AbstractExcelSheet.java index fd5b5934c..f253b1ffd 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/AbstractExcelSheet.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/AbstractExcelSheet.java @@ -1,13 +1,16 @@ package org.devgateway.ocds.persistence.mongo.excel; +import org.apache.poi.common.usermodel.HyperlinkType; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CreationHelper; import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.Hyperlink; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import java.math.BigDecimal; @@ -74,20 +77,20 @@ public AbstractExcelSheet(final Workbook workbook) { this.linkFont.setUnderline(Font.U_SINGLE); this.dataStyleCell = this.workbook.createCellStyle(); - this.dataStyleCell.setAlignment(CellStyle.ALIGN_LEFT); - this.dataStyleCell.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + this.dataStyleCell.setAlignment(HorizontalAlignment.LEFT); + this.dataStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); this.dataStyleCell.setWrapText(true); this.dataStyleCell.setFont(this.dataFont); this.headerStyleCell = this.workbook.createCellStyle(); - this.headerStyleCell.setAlignment(CellStyle.ALIGN_CENTER); - this.headerStyleCell.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + this.headerStyleCell.setAlignment(HorizontalAlignment.CENTER); + this.headerStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); this.headerStyleCell.setWrapText(true); this.headerStyleCell.setFont(this.headerFont); this.linkStyleCell = this.workbook.createCellStyle(); - this.linkStyleCell.setAlignment(CellStyle.ALIGN_LEFT); - this.linkStyleCell.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + this.linkStyleCell.setAlignment(HorizontalAlignment.LEFT); + this.linkStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); this.linkStyleCell.setWrapText(true); this.linkStyleCell.setFont(this.linkFont); } @@ -154,7 +157,7 @@ public void writeCell(final Object value, final Row row, final int column) { } /** - * Creates a cell that is a link to another sheet in the document {@link Hyperlink#LINK_DOCUMENT}. + * Creates a cell that is a link to another sheet in the document * * @param value * @param row @@ -166,7 +169,7 @@ public void writeCellLink(final Object value, final Row row, final int column, final String sheetName, final int rowNumber) { this.writeCell(value, row, column); - Hyperlink link = createHelper.createHyperlink(Hyperlink.LINK_DOCUMENT); + Hyperlink link = createHelper.createHyperlink(HyperlinkType.DOCUMENT); // always point to first column A in excel file link.setAddress("'" + sheetName + "'!A" + rowNumber); cell.setHyperlink(link); diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/FieldType.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/FieldType.java index f974898ce..7331928a9 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/FieldType.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/FieldType.java @@ -9,6 +9,7 @@ import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import java.math.BigDecimal; +import java.net.URI; import java.util.Date; /** @@ -45,5 +46,6 @@ private FieldType() { .add(long.class) .add(Double.class) .add(double.class) + .add(URI.class) .build(); } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/Flag.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/Flag.java index d853d621d..f98728b02 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/Flag.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/Flag.java @@ -1,8 +1,9 @@ package org.devgateway.ocds.persistence.mongo.flags; +import java.io.Serializable; import java.util.Set; -public class Flag { +public class Flag implements Serializable { private Set types; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/ReleaseFlags.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/ReleaseFlags.java index 12542541a..71694c861 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/ReleaseFlags.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/ReleaseFlags.java @@ -5,13 +5,14 @@ import org.springframework.data.annotation.Transient; +import java.io.Serializable; import java.util.Collection; import java.util.HashMap; /** * @author mpostelnicu Represents the list of red flags at the Release level */ -public class ReleaseFlags implements FlagsWrappable { +public class ReleaseFlags implements FlagsWrappable, Serializable { // i038: Competitive tender w/ short bidding period private Flag i038; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/preconditions/FlaggedReleasePredicates.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/preconditions/FlaggedReleasePredicates.java index 4ee527476..847047abd 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/preconditions/FlaggedReleasePredicates.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/flags/preconditions/FlaggedReleasePredicates.java @@ -31,46 +31,65 @@ private FlaggedReleasePredicates() { && p.getTender().getTenderPeriod() != null && p.getTender().getTenderPeriod().getEndDate() != null); public static final NamedPredicate OPEN_PROCUREMENT_METHOD = - new NamedPredicate<>("Needs to have open tender procurement method", + new NamedPredicate<>( + "Needs to have open tender procurement method", p -> p.getTender() != null - && Tender.ProcurementMethod.open.equals(p.getTender().getProcurementMethod())); + && Tender.ProcurementMethod.open.equals(p.getTender().getProcurementMethod()) + ); public static final NamedPredicate SELECTIVE_PROCUREMENT_METHOD = - new NamedPredicate<>("Needs to have selective tender procurement method", + new NamedPredicate<>( + "Needs to have selective tender procurement method", p -> p.getTender() != null - && Tender.ProcurementMethod.selective.equals(p.getTender().getProcurementMethod())); + && Tender.ProcurementMethod.selective.equals(p.getTender().getProcurementMethod()) + ); public static final NamedPredicate LIMITED_PROCUREMENT_METHOD = - new NamedPredicate<>("Needs to have limited tender procurement method", + new NamedPredicate<>( + "Needs to have limited tender procurement method", p -> p.getTender() != null - && Tender.ProcurementMethod.limited.equals(p.getTender().getProcurementMethod())); + && Tender.ProcurementMethod.limited.equals(p.getTender().getProcurementMethod()) + ); public static final NamedPredicate ACTIVE_AWARD_WITH_DATE = - new NamedPredicate<>("Needs to have at least one active award", - p -> p.getAwards().stream().filter(a -> a.getDate() != null - && Award.Status.active.equals(a.getStatus())).count() > 0); + new NamedPredicate<>( + "Needs to have at least one active award with date", + p -> p.getAwards().stream().anyMatch(a -> a.getDate() != null + && Award.Status.active.equals(a.getStatus())) + ); public static final NamedPredicate ACTIVE_AWARD = - new NamedPredicate<>("Needs to have at least one active award", - p -> p.getAwards().stream().filter(a -> Award.Status.active.equals(a.getStatus())).count() > 0); + new NamedPredicate<>( + "Needs to have at least one active award", + p -> p.getAwards() + .stream().anyMatch(a -> Award.Status.active.equals(a.getStatus())) + ); public static final NamedPredicate AWARDED_AMOUNT = - new NamedPredicate<>("Needs to have at least one award with awarded amount", - p -> p.getAwards().stream().filter(a -> a.getValue() != null - && a.getValue().getAmount() != null).count() > 0); + new NamedPredicate<>( + "Needs to have at least one award with awarded amount", + p -> p.getAwards().stream().anyMatch(a -> a.getValue() != null + && a.getValue().getAmount() != null) + ); public static final NamedPredicate TENDER_ITEMS_CLASSIFICATION = - new NamedPredicate<>("Needs to have tender with items classification", + new NamedPredicate<>( + "Needs to have tender with items classification", p -> p.getTender() != null && !p.getTender().getItems().isEmpty() - && p.getTender().getItems().stream().findFirst().get().getClassification() != null); + && p.getTender().getItems().stream().findFirst().get().getClassification() != null + ); public static final NamedPredicate UNSUCCESSFUL_AWARD = new NamedPredicate<>( "Needs to have at least one unsuccessful award", - p -> p.getAwards().stream().filter(a -> Award.Status.unsuccessful.equals(a.getStatus())).count() > 0); + p -> p.getAwards().stream().anyMatch(a -> Award.Status.unsuccessful.equals(a.getStatus())) + ); public static final NamedPredicate ELECTRONIC_SUBMISSION = new NamedPredicate<>( "Needs to have electronic submission tender submission method", p -> p.getTender() != null && p.getTender().getSubmissionMethod() != null - && p.getTender().getSubmissionMethod().contains(Tender.SubmissionMethod.electronicSubmission)); + && p.getTender() + .getSubmissionMethod() + .contains(Tender.SubmissionMethod.electronicSubmission.toString()) + ); } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java new file mode 100644 index 000000000..cb64e794a --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java @@ -0,0 +1,11 @@ +package org.devgateway.ocds.persistence.mongo.reader; + +/** + * Created by mpostelnicu on 6/23/17. + */ +public class ImportWarningRuntimeException extends RuntimeException { + + public ImportWarningRuntimeException(String var1) { + super(var1); + } +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java index 213cf35d7..43462fb3e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java @@ -1,11 +1,11 @@ package org.devgateway.ocds.persistence.mongo.reader; +import java.text.ParseException; +import java.util.Date; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.ImportService; -import java.text.ParseException; - /** * @author mpostelnicu */ @@ -19,6 +19,7 @@ public ReleaseRowImporter(final ReleaseRepository releaseRepository, final Impor @Override public void importRow(final String[] row) throws ParseException { Release release = createReleaseFromReleaseRow(row); + release.setDate(new Date()); if (release.getId() == null) { repository.insert(release); } else { diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java index ed9fd1e1c..84e03b205 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java @@ -16,13 +16,10 @@ /** * Generic superclass for importing rows from excel data sources * - * @author mpostelnicu - * - * @param - * - the type of OCDS/dervied entity to be imported + * @param - the type of OCDS/dervied entity to be imported * @param the id type - * @param - * - the main repository that is able to save + * @param - the main repository that is able to save + * @author mpostelnicu */ public abstract class RowImporter> { @@ -101,7 +98,7 @@ private boolean isRowEmpty(final String[] row) { } public boolean importRows(final List rows) throws ParseException { - + boolean r = true; for (String[] row : rows) { if (cursorRowNo++ < skipRows || isRowEmpty(row)) { continue; @@ -111,14 +108,21 @@ public boolean importRows(final List rows) throws ParseException { importRow(row); importedRows++; } catch (Exception e) { + boolean criticalError = true; + if (e instanceof ImportWarningRuntimeException) { + criticalError = false; + } else { + r = false; + } importService.logMessage( - "Error importing row " + cursorRowNo + ". " + e + ""); - // throw e; we do not stop + "" + + (criticalError ? "CRITICAL " : "") + "Problem importing row " + + cursorRowNo + ". " + e + ""); } } logger.debug("Finished importing " + importedRows + " rows."); - return true; + return r; } public abstract void importRow(String[] row) throws ParseException; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImport.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImport.java index 7da219796..86ed4533b 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImport.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImport.java @@ -4,7 +4,7 @@ import org.apache.commons.digester3.binder.AbstractRulesModule; import org.apache.commons.digester3.binder.DigesterLoader; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.xml.sax.SAXException; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ClassificationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ClassificationRepository.java similarity index 93% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ClassificationRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ClassificationRepository.java index 829c07166..affdbb5c4 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ClassificationRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ClassificationRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Classification; import org.springframework.cache.annotation.CacheConfig; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/DefaultLocationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/DefaultLocationRepository.java similarity index 94% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/DefaultLocationRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/DefaultLocationRepository.java index a1e9b9881..34c34d5b7 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/DefaultLocationRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/DefaultLocationRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.DefaultLocation; import org.springframework.cache.annotation.CacheConfig; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/FlaggedReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/FlaggedReleaseRepository.java similarity index 76% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/FlaggedReleaseRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/FlaggedReleaseRepository.java index e3c7ee9d5..bb2c2fa0c 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/FlaggedReleaseRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/FlaggedReleaseRepository.java @@ -1,7 +1,7 @@ /** * */ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.FlaggedRelease; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericOrganizationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericOrganizationRepository.java similarity index 89% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericOrganizationRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericOrganizationRepository.java index 6e05f40b1..bf147c73b 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericOrganizationRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericOrganizationRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Organization; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericReleaseRepository.java similarity index 90% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericReleaseRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericReleaseRepository.java index 6e1c3f166..3e676fb56 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericReleaseRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericReleaseRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Release; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/OrganizationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/OrganizationRepository.java similarity index 93% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/OrganizationRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/OrganizationRepository.java index c6f5d61e5..bc70babe0 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/OrganizationRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/OrganizationRepository.java @@ -1,15 +1,15 @@ /** * */ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; -import java.util.Collection; -import java.util.List; - -import org.devgateway.ocds.persistence.mongo.Organization; import org.devgateway.ocds.persistence.mongo.Organization.OrganizationType; +import org.devgateway.ocds.persistence.mongo.Organization; import org.springframework.data.mongodb.repository.Query; +import java.util.Collection; +import java.util.List; + /** * @author mpostelnicu * diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/RecordRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/RecordRepository.java similarity index 79% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/RecordRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/RecordRepository.java index 8c1f5be09..388d6661a 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/RecordRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/RecordRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Record; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ReleaseRepository.java similarity index 74% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ReleaseRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ReleaseRepository.java index 1b761ba0a..44a55500f 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ReleaseRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ReleaseRepository.java @@ -1,7 +1,7 @@ /** * */ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Release; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowClassificationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowClassificationRepository.java new file mode 100644 index 000000000..5eb492bf1 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowClassificationRepository.java @@ -0,0 +1,7 @@ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +import org.devgateway.ocds.persistence.mongo.repository.main.ClassificationRepository; + +public interface ShadowClassificationRepository extends ClassificationRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowFlaggedReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowFlaggedReleaseRepository.java new file mode 100644 index 000000000..ab5e42968 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowFlaggedReleaseRepository.java @@ -0,0 +1,12 @@ +/** + * + */ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +/** + * @author mpostelnicu + * + */ +public interface ShadowFlaggedReleaseRepository extends ShadowReleaseRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowOrganizationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowOrganizationRepository.java new file mode 100644 index 000000000..62f1704b2 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowOrganizationRepository.java @@ -0,0 +1,14 @@ +/** + * + */ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +import org.devgateway.ocds.persistence.mongo.repository.main.OrganizationRepository; + +/** + * @author mpostelnicu + * + */ +public interface ShadowOrganizationRepository extends OrganizationRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowRecordRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowRecordRepository.java new file mode 100644 index 000000000..ab8d44ee2 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowRecordRepository.java @@ -0,0 +1,7 @@ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +import org.devgateway.ocds.persistence.mongo.repository.main.RecordRepository; + +public interface ShadowRecordRepository extends RecordRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowReleaseRepository.java new file mode 100644 index 000000000..ace7f0f39 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowReleaseRepository.java @@ -0,0 +1,14 @@ +/** + * + */ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; + +/** + * @author mpostelnicu + * + */ +public interface ShadowReleaseRepository extends ReleaseRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ExcelImportService.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ExcelImportService.java index 79c0a9b02..439d4bd0d 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ExcelImportService.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ExcelImportService.java @@ -1,10 +1,16 @@ package org.devgateway.ocds.persistence.mongo.spring; +import java.util.List; + /** * @author idobre * @since 5/20/16 - * + *

* Service that imports Excel sheets in OCDS format */ public interface ExcelImportService extends ImportService { + + ImportResult importAllSheets(List fileTypes, byte[] prototypeDatabase, byte[] + locations, byte[] publicInstitutionsSuppliers, byte[] cdg, Boolean purgeDatabase, + Boolean validateData, Boolean flagData) throws InterruptedException; } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java new file mode 100644 index 000000000..2dd9396cb --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java @@ -0,0 +1,32 @@ +package org.devgateway.ocds.persistence.mongo.spring; + +/** + * Created by mpost on 21-Jun-17. + */ +public class ImportResult { + + public ImportResult(Boolean success, StringBuffer msgBuffer) { + this.success = success; + this.msgBuffer = msgBuffer; + } + + private Boolean success; + + private StringBuffer msgBuffer; + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public StringBuffer getMsgBuffer() { + return msgBuffer; + } + + public void setMsgBuffer(StringBuffer msgBuffer) { + this.msgBuffer = msgBuffer; + } +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidationConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidationConfiguration.java index 55ed0ea21..0f67346be 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidationConfiguration.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidationConfiguration.java @@ -21,15 +21,15 @@ public OcdsSchemaValidatorService ocdsSchemaValidatorService() { return jsonSchemaValidation; } - @Bean("ocdsSchemaAllRequiredValidator") - public OcdsSchemaValidatorService ocdsSchemaAllRequiredValidatorService() { - OcdsSchemaValidatorService jsonSchemaValidation = new OcdsSchemaValidatorService(jacksonObjectMapper, - OcdsSchemaValidatorService.OCDS_SCHEMA_ALL_REQUIRED); - jsonSchemaValidation - .withJsonPatches(OcdsSchemaValidatorService.OCDS_LOCATION_PATCH_LOCATION, - OcdsSchemaValidatorService.OCDS_BID_EXTENSION) - .init(); - return jsonSchemaValidation; - } +// @Bean("ocdsSchemaAllRequiredValidator") +// public OcdsSchemaValidatorService ocdsSchemaAllRequiredValidatorService() { +// OcdsSchemaValidatorService jsonSchemaValidation = new OcdsSchemaValidatorService(jacksonObjectMapper, +// OcdsSchemaValidatorService.OCDS_SCHEMA_ALL_REQUIRED); +// jsonSchemaValidation +// .withJsonPatches(OcdsSchemaValidatorService.OCDS_LOCATION_PATCH_LOCATION, +// OcdsSchemaValidatorService.OCDS_BID_EXTENSION) +// .init(); +// return jsonSchemaValidation; +// } } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidatorService.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidatorService.java index 395825a3d..3fdfbf3c1 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidatorService.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidatorService.java @@ -16,12 +16,13 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.main.JsonSchema; import com.github.fge.jsonschema.main.JsonSchemaFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * @author mpostelnicu @@ -32,7 +33,7 @@ public class OcdsSchemaValidatorService { private JsonSchema schema; public static final String OCDS_SCHEMA_LOCATION = "/release-schema.json"; - public static final String OCDS_SCHEMA_ALL_REQUIRED = "/release-schema-all-required.json"; +// public static final String OCDS_SCHEMA_ALL_REQUIRED = "/release-schema-all-required.json"; public static final String OCDS_LOCATION_PATCH_LOCATION = "/location_patch_schema.json"; public static final String OCDS_BID_EXTENSION = "/ocds_bid_extension.mergepatch.json"; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseCompilerService.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseCompilerService.java index 94faebf19..6ec96981b 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseCompilerService.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseCompilerService.java @@ -22,8 +22,8 @@ import org.devgateway.ocds.persistence.mongo.Tag; import org.devgateway.ocds.persistence.mongo.merge.Merge; import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import org.devgateway.ocds.persistence.mongo.repository.RecordRepository; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.RecordRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseRecordMonitor.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseRecordMonitor.java index 426fdc97c..b016e0e8a 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseRecordMonitor.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseRecordMonitor.java @@ -9,14 +9,11 @@ import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.Aspect; import org.devgateway.ocds.persistence.mongo.Record; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.RecordRepository; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.RecordRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; /** * @author mpostelnicu diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImport.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImport.java index 21ce6e527..da5a65446 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImport.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImport.java @@ -2,7 +2,7 @@ import org.apache.log4j.Logger; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json2object.JsonToObject; import org.devgateway.ocds.persistence.mongo.spring.json2object.ReleaseJsonToObject; import org.springframework.transaction.annotation.Transactional; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImport.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImport.java index 1a3e96ef5..1490b80f4 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImport.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImport.java @@ -3,7 +3,7 @@ import org.apache.log4j.Logger; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json2object.JsonToObject; import org.devgateway.ocds.persistence.mongo.spring.json2object.ReleasePackageJsonToObject; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json2object/AbstractJsonToObject.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json2object/AbstractJsonToObject.java index 1c0264e07..cbb06931d 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json2object/AbstractJsonToObject.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json2object/AbstractJsonToObject.java @@ -35,8 +35,8 @@ public AbstractJsonToObject(final String jsonObject) { mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - mapper.configure(JsonParser.Feature.IGNORE_UNDEFINED, true); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + mapper.configure(JsonParser.Feature.IGNORE_UNDEFINED, false); // Note that enabling this feature will incur performance overhead // due to having to store and check additional information: this typically diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties index 6aa0df64e..8afcf639e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties @@ -12,4 +12,4 @@ security.basic.enabled=false server.port = 8090 -spring.data.mongodb.uri=mongodb://localhost:27017/ocexplorer +spring.data.mongodb.uri=mongodb://localhost:27017/ocexplorer \ No newline at end of file diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/repository/CustomerRepository.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/repository/CustomerRepository.java deleted file mode 100644 index 3c9b15607..000000000 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/repository/CustomerRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.devgateway.toolkit.persistence.mongo.repository; - -import java.util.List; - -import org.devgateway.toolkit.persistence.mongo.dao.Customer; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.repository.query.Param; - -public interface CustomerRepository extends MongoRepository { - - List findByFirstName(@Param("firstName") String firstName); - - List findByLastName(@Param("lastName") String lastName); - -} \ No newline at end of file diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java new file mode 100644 index 000000000..176c41853 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java @@ -0,0 +1,157 @@ +package org.devgateway.toolkit.persistence.mongo.spring; + +import org.apache.commons.io.IOUtils; +import org.devgateway.ocds.persistence.mongo.DefaultLocation; +import org.devgateway.ocds.persistence.mongo.Organization; +import org.devgateway.ocds.persistence.mongo.Release; +import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; +import org.devgateway.ocds.persistence.mongo.flags.FlagsConstants; +import org.slf4j.Logger; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.ScriptOperations; +import org.springframework.data.mongodb.core.index.Index; +import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder; +import org.springframework.data.mongodb.core.script.ExecutableMongoScript; +import org.springframework.data.mongodb.core.script.NamedMongoScript; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.net.URL; + +import static org.devgateway.ocds.persistence.mongo.constants.MongoConstants.FieldNames.FLAGS_TOTAL_FLAGGED; + +public abstract class AbstractMongoDatabaseConfiguration { + + protected abstract Logger getLogger(); + + protected abstract MongoTemplate getTemplate(); + + public void createMandatoryImportIndexes() { + //mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("planning.budget.projectID", Direction.ASC)); + //mongoTemplate.indexOps(Location.class).ensureIndex(new Index().on("description", Direction.ASC)); + getTemplate().indexOps(Organization.class).ensureIndex(new Index().on("identifier._id", Direction.ASC)); + getTemplate().indexOps(Organization.class) + .ensureIndex(new Index().on("additionalIdentifiers._id", Direction.ASC)); + getTemplate().indexOps(Organization.class).ensureIndex( + new Index().on("roles", Direction.ASC)); + getTemplate().indexOps(Organization.class).ensureIndex(new Index().on("name", Direction.ASC).unique()); + getTemplate().indexOps(DefaultLocation.class).ensureIndex(new Index().on("description", Direction.ASC)); + getLogger().info("Added mandatory Mongo indexes"); + } + + public void createCorruptionFlagsIndexes() { + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FLAGS_TOTAL_FLAGGED, Direction.ASC)); + + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("flags.flaggedStats.type", Direction.ASC) + .on("flags.flaggedStats.count", Direction.ASC) + ); + + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("flags.eligibleStats.type", Direction.ASC) + .on("flags.eligibleStats.count", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I038_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I007_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I004_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I077_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I180_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I019_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I002_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I085_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I171_VALUE, Direction.ASC)); + getLogger().info("Added corruption flags indexes"); + } + + @PostConstruct + public void mongoPostInit() { + createMandatoryImportIndexes(); + createPostImportStructures(); + } + + public void createPostImportStructures() { + + createCorruptionFlagsIndexes(); + + + // initialize some extra indexes + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("ocid", Direction.ASC).unique()); + + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.TENDER_PROC_METHOD, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on("tender.procurementMethodRationale", Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.TENDER_STATUS, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.AWARDS_STATUS, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.AWARDS_SUPPLIERS_ID, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.AWARDS_SUPPLIERS_NAME, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.AWARDS_DATE, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.AWARDS_VALUE_AMOUNT, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.TENDER_VALUE_AMOUNT, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.TENDER_NO_TENDERERS, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on( + MongoConstants.FieldNames.TENDER_SUBMISSION_METHOD, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on(MongoConstants.FieldNames.TENDER_PERIOD_START_DATE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index() + .on(MongoConstants.FieldNames.TENDER_PERIOD_END_DATE, Direction.ASC)); + getTemplate().indexOps(Release.class) + .ensureIndex(new Index().on("tender.items.classification._id", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index(). + on("tender.items.deliveryLocation._id", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index(). + on("tender.items.deliveryLocation.geometry.coordinates", Direction.ASC)); + + getTemplate().indexOps(Release.class).ensureIndex(new Index(). + on(MongoConstants.FieldNames.BIDS_DETAILS_TENDERERS_ID, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index(). + on(MongoConstants.FieldNames.BIDS_DETAILS_VALUE_AMOUNT, Direction.ASC)); + + + getTemplate().indexOps(Organization.class).ensureIndex(new TextIndexDefinitionBuilder() + .withDefaultLanguage(MongoConstants.MONGO_LANGUAGE) + .onField("name") + .onField("id").onField("additionalIdentifiers._id").build()); + + getTemplate().indexOps(Release.class).ensureIndex(new TextIndexDefinitionBuilder() + .named("text_search") + .withDefaultLanguage(MongoConstants.MONGO_LANGUAGE) + .onFields("tender.title", "tender.description", + "tender.procuringEntity.name", "tender.id", "tender.procuringEntity.description", + "awards.id", "awards.description", "awards.suppliers.name", "awards.suppliers.description", + "ocid", "buyer.name", "buyer.id" + ).build()); + + getLogger().info("Added extra Mongo indexes"); + + ScriptOperations scriptOps = getTemplate().scriptOps(); + + // add script to calculate the percentiles endpoint + URL scriptFile = getClass().getResource("/tenderBidPeriodPercentilesMongo.js"); + try { + String scriptText = IOUtils.toString(scriptFile); + ExecutableMongoScript script = new ExecutableMongoScript(scriptText); + scriptOps.register(new NamedMongoScript("tenderBidPeriodPercentiles", script)); + } catch (IOException e) { + e.printStackTrace(); + } + + // add general mongo system helper methods + URL systemScriptFile = getClass().getResource("/mongoSystemScripts.js"); + try { + String systemScriptFileText = IOUtils.toString(systemScriptFile); + ExecutableMongoScript script = new ExecutableMongoScript(systemScriptFileText); + scriptOps.execute(script); + } catch (IOException e) { + e.printStackTrace(); + } + + } + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoDatabaseConfiguration.java new file mode 100644 index 000000000..8640c075f --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoDatabaseConfiguration.java @@ -0,0 +1,34 @@ +package org.devgateway.toolkit.persistence.mongo.spring; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * Created by mpostelnicu on 6/12/17. + */ +@Configuration +@EnableMongoRepositories( + basePackages = {"org.devgateway.ocds.persistence.mongo.repository.main"}, + mongoTemplateRef = "mongoTemplate" +) +public class MongoDatabaseConfiguration extends AbstractMongoDatabaseConfiguration { + + protected final Logger logger = LoggerFactory.getLogger(MongoDatabaseConfiguration.class); + + @Autowired + private MongoTemplate mongoTemplate; + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + protected MongoTemplate getTemplate() { + return mongoTemplate; + } +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java index c688a35bc..dc718748d 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java @@ -19,10 +19,8 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.PropertySource; import org.springframework.core.convert.converter.Converter; -import org.springframework.data.mongodb.config.EnableMongoAuditing; import org.springframework.data.mongodb.core.convert.CustomConversions; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; -import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -39,8 +37,6 @@ @SpringBootApplication @ComponentScan("org.devgateway") @PropertySource("classpath:/org/devgateway/toolkit/persistence/mongo/application.properties") -@EnableMongoRepositories(basePackages = "org.devgateway") -@EnableMongoAuditing @EnableCaching public class MongoPersistenceApplication { diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java new file mode 100644 index 000000000..3e7554f81 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java @@ -0,0 +1,52 @@ +package org.devgateway.toolkit.persistence.mongo.spring; + +import com.mongodb.MongoClientURI; +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.mongo.MongoProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoDbFactory; +import org.springframework.data.mongodb.core.convert.CustomConversions; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; + +/** + * Created by mpostelnicu on 6/12/17. + */ +@Configuration +@Profile("!integration") +public class MongoTemplateConfig { + + public static final String SHADOW_POSTFIX = "-shadow"; + + @Autowired + private MongoProperties properties; + + @Autowired + private CustomConversions customConversions; + + @Bean(autowire = Autowire.BY_NAME, name = "mongoTemplate") + public MongoTemplate mongoTemplate() throws Exception { + MongoTemplate template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri()))); + ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); + return template; + } + + /** + * Creates a shadow template configuration by adding "-shadow" as postfix of database name. + * This is used to replicate the entire database structure in a shadow/temporary database location + * + * @return + * @throws Exception + */ + @Bean(autowire = Autowire.BY_NAME, name = "shadowMongoTemplate") + public MongoTemplate shadowMongoTemplate() throws Exception { + MongoTemplate template = new + MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri() + SHADOW_POSTFIX))); + ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); + return template; + } + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfiguration.java deleted file mode 100644 index 77b3e8e70..000000000 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfiguration.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.devgateway.toolkit.persistence.mongo.spring; - -import org.apache.commons.io.IOUtils; -import org.devgateway.ocds.persistence.mongo.DefaultLocation; -import org.devgateway.ocds.persistence.mongo.Organization; -import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; -import org.devgateway.ocds.persistence.mongo.flags.FlagsConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.domain.Sort.Direction; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.ScriptOperations; -import org.springframework.data.mongodb.core.index.Index; -import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder; -import org.springframework.data.mongodb.core.script.ExecutableMongoScript; -import org.springframework.data.mongodb.core.script.NamedMongoScript; - -import javax.annotation.PostConstruct; -import java.io.IOException; -import java.net.URL; - -@Configuration -public class MongoTemplateConfiguration { - - private final Logger logger = LoggerFactory.getLogger(MongoTemplateConfiguration.class); - - @Autowired - private MongoTemplate mongoTemplate; - - public void createMandatoryImportIndexes() { - //mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("planning.budget.projectID", Direction.ASC)); - //mongoTemplate.indexOps(Location.class).ensureIndex(new Index().on("description", Direction.ASC)); - mongoTemplate.indexOps(Organization.class).ensureIndex(new Index().on("identifier._id", Direction.ASC)); - mongoTemplate.indexOps(Organization.class) - .ensureIndex(new Index().on("additionalIdentifiers._id", Direction.ASC)); - mongoTemplate.indexOps(Organization.class).ensureIndex( - new Index().on("roles", Direction.ASC)); - mongoTemplate.indexOps(Organization.class).ensureIndex(new Index().on("name", Direction.ASC).unique()); - mongoTemplate.indexOps(DefaultLocation.class).ensureIndex(new Index().on("description", Direction.ASC)); - logger.info("Added mandatory Mongo indexes"); - } - - public void createCorruptionFlagsIndexes() { - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("flags.totalFlagged", Direction.ASC)); - - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("flags.flaggedStats.type", Direction.ASC) - .on("flags.flaggedStats.count", Direction.ASC) - ); - - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("flags.eligibleStats.type", Direction.ASC) - .on("flags.eligibleStats.count", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I038_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I007_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I004_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I077_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I180_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I019_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I002_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I085_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I171_VALUE, Direction.ASC)); - } - - @PostConstruct - public void mongoPostInit() { - createMandatoryImportIndexes(); - createPostImportStructures(); - } - - public void createPostImportStructures() { - - createCorruptionFlagsIndexes(); - - // initialize some extra indexes - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("ocid", Direction.ASC).unique()); - - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.procurementMethod", Direction.ASC)); - mongoTemplate.indexOps(Release.class) - .ensureIndex(new Index().on("tender.procurementMethodRationale", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.status", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("awards.status", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("awards.suppliers._id", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("awards.date", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("awards.value.amount", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.value.amount", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.numberOfTenderers", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.submissionMethod", Direction.ASC)); - mongoTemplate.indexOps(Release.class) - .ensureIndex(new Index().on(MongoConstants.FieldNames.TENDER_PERIOD_START_DATE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index() - .on(MongoConstants.FieldNames.TENDER_PERIOD_END_DATE, Direction.ASC)); - mongoTemplate.indexOps(Release.class) - .ensureIndex(new Index().on("tender.items.classification._id", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index(). - on("tender.items.deliveryLocation._id", Direction.ASC)); - - mongoTemplate.indexOps(Release.class).ensureIndex(new Index(). - on("tender.items.deliveryLocation.geometry.coordinates", Direction.ASC)); - - mongoTemplate.indexOps(Organization.class).ensureIndex(new TextIndexDefinitionBuilder().onField("name") - .onField("id").onField("additionalIdentifiers._id").build()); - - logger.info("Added extra Mongo indexes"); - - ScriptOperations scriptOps = mongoTemplate.scriptOps(); - - // add script to calculate the percentiles endpoint - URL scriptFile = getClass().getResource("/tenderBidPeriodPercentilesMongo.js"); - try { - String scriptText = IOUtils.toString(scriptFile); - ExecutableMongoScript script = new ExecutableMongoScript(scriptText); - scriptOps.register(new NamedMongoScript("tenderBidPeriodPercentiles", script)); - } catch (IOException e) { - e.printStackTrace(); - } - - // add general mongo system helper methods - URL systemScriptFile = getClass().getResource("/mongoSystemScripts.js"); - try { - String systemScriptFileText = IOUtils.toString(systemScriptFile); - ExecutableMongoScript script = new ExecutableMongoScript(systemScriptFileText); - scriptOps.execute(script); - } catch (IOException e) { - e.printStackTrace(); - } - - } - -} diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoUtil.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoUtil.java index 39964611d..86506fdd8 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoUtil.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoUtil.java @@ -17,7 +17,7 @@ private MongoUtil() { } - public static final int BATCH_SIZE = 5000; + public static final int BATCH_SIZE = 10000; public static void processRepositoryItemsPaginated(MongoRepository repository, diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java new file mode 100644 index 000000000..019f20bf3 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java @@ -0,0 +1,34 @@ +package org.devgateway.toolkit.persistence.mongo.spring; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * Created by mpostelnicu on 6/12/17. + */ +@Configuration +@EnableMongoRepositories( + basePackages = {"org.devgateway.ocds.persistence.mongo.repository.shadow"}, + mongoTemplateRef = "shadowMongoTemplate" +) +public class ShadowMongoDatabaseConfiguration extends AbstractMongoDatabaseConfiguration { + + protected final Logger logger = LoggerFactory.getLogger(ShadowMongoDatabaseConfiguration.class); + + @Autowired + private MongoTemplate shadowMongoTemplate; + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + protected MongoTemplate getTemplate() { + return shadowMongoTemplate; + } +} diff --git a/persistence-mongodb/src/main/resources/record-package-schema-modified-by-dg.json b/persistence-mongodb/src/main/resources/record-package-schema-modified-by-dg.json deleted file mode 100644 index 14379c04f..000000000 --- a/persistence-mongodb/src/main/resources/record-package-schema-modified-by-dg.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "id": "http://ocds.open-contracting.org/standard/r/1__0__0/record-package-schema.json", - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Schema for an Open Contracting Record package", - "description": "The record package contains a list of records along with some publishing meta data. The records pull together all the releases under a single Open Contracting ID and compile them into the latest version of the information along with the history of any data changes.", - "type": "object", - "properties": { - "uri": { - "title": "Package Identifier", - "description": "The URI of this package that identifies it uniquely in the world.", - "type": "string", - "format": "uri" - }, - "publisher": { - "description": "Information to uniquely identify the publisher of this package.", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "scheme": { - "description": "The scheme that holds the unique identifiers used to identify the item being identified.", - "type": ["string", "null"], - "format": "uri" - }, - "uid": { - "description": "The unique ID for this entity under the given ID scheme.", - "type": ["string", "null"] - }, - "uri": { - "type": ["string", "null"], - "format" : "uri" - } - }, - "required": ["name"] - }, - "license": { - "description": "A link to the license that applies to the data in this datapackage. [Open Definition Conformant](http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", - "type": ["string", "null"], - "format": "uri" - }, - "publicationPolicy": { - "description": "A link to a document describing the publishers publication policy.", - "type": ["string", "null"], - "format": "uri" - }, - "publishedDate": { - "description": "The date that this package was published.", - "type": "string", - "format": "date-time" - }, - "packages": { - "description": "A list of URIs of all the release packages that were used to create this record package.", - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "format": "uri" - }, - "uniqueItems": true - }, - "records": { - "description": "The records for this data package.", - "type": "array", - "minItems": 1, - "items": { "$ref": "#/definitions/record" }, - "uniqueItems": true - } - }, - "required": ["uri", "publisher", "publishedDate", "packages", "records"], - "definitions": { - "record": { - "type": "object", - "properties": { - "ocid": { - "title": "Open Contracting ID", - "description": "A unique identifier that identifies the unique Open Contracting Process. For more information see: http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/definitions/#contracting-process", - "type": "string" - }, - "releases": { - "title": "Linked releases", - "description": "A list of objects that identify the releases associated with this Open Contracting ID. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", - "type": "array", - "items": { - "description": "Information to uniquely identify the release.", - "type": "object", - "properties": { - "url": { - "description": "The url of the release which contains the url of the package with the releaseID appended using a fragment identifier e.g. http://ocds.open-contracting.org/demos/releases/12345.json#ocds-a2ef3d01-1594121/1", - "type": ["string", "null"], - "format" : "uri" - }, - "date": { - "title": "Release Date", - "description": "The date of the release, should match `date` at the root level of the release. This is used to sort the releases in the list into date order.", - "type": "string", - "format": "date-time" - }, - "tag": { - "title": "Release Tag", - "description": "The tag should match the tag in the release. This provides additional context when reviewing a record to see what types of releases are included for this ocid.", - "type": "array", - "items": { - "type": "string", - "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] - } - } - }, - "required": ["url", "date"] - }, - "minItems": 1 - }, - "compiledRelease": { - "title": "Compiled release", - "description": "This is the latest version of all the contracting data, it has the same schema as an open contracting release.", - "$ref": "http://ocds.open-contracting.org/standard/r/1__0__0/release-schema.json" - } - }, - "required": ["ocid", "releases"] - } - } -} \ No newline at end of file diff --git a/persistence-mongodb/src/main/resources/record-package-schema.json b/persistence-mongodb/src/main/resources/record-package-schema.json new file mode 100644 index 000000000..85d4186d6 --- /dev/null +++ b/persistence-mongodb/src/main/resources/record-package-schema.json @@ -0,0 +1,217 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Record package", + "description": "The record package contains a list of records along with some publishing metadata. The records pull together all the releases under a single Open Contracting ID and compile them into the latest version of the information along with the history of any data changes.", + "type": "object", + "properties": { + "uri": { + "title": "Package identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "version": { + "title": "OCDS schema version", + "description": "The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "extensions": { + "title": "OCDS extensions", + "description": "An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for that extension.", + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the organization or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title": "Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": [ + "string", + "null" + ] + }, + "uid": { + "title": "uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the publisher.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "required": [ + "name" + ] + }, + "license": { + "title": "License", + "description": "A link to the license that applies to the data in this data package. [Open Definition Conformant](http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "publicationPolicy": { + "title": "Publication policy", + "description": "A link to a document describing the publishers publication policy.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "publishedDate": { + "title": "Published date", + "description": "The date that this package was published. If this package is generated 'on demand', this date should reflect the date of the last change to the underlying contents of the package.", + "type": "string", + "format": "date-time" + }, + "packages": { + "title": "Packages", + "description": "A list of URIs of all the release packages that were used to create this record package.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "format": "uri" + }, + "uniqueItems": true + }, + "records": { + "title": "Records", + "description": "The records for this data package.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/record" + }, + "uniqueItems": true + } + }, + "required": [ + "uri", + "publisher", + "publishedDate", + "records", + "version" + ], + "definitions": { + "record": { + "title": "Record", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A unique identifier that identifies the unique Open Contracting Process. For more information see: http://standard.open-contracting.org/latest/en/getting_started/contracting_process/", + "type": "string" + }, + "releases": { + "title": "Releases", + "description": "An array of linking identifiers or releases", + "oneOf": [ + { + "title": "Linked releases", + "description": "A list of objects that identify the releases associated with this Open Contracting ID. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "description": "Information to uniquely identify the release.", + "type": "object", + "properties": { + "url": { + "description": "The URL of the release which contains the URL of the package with the releaseID appended using a fragment identifier e.g. http://standard.open-contracting.org/{{version}}/{{lang}}/examples/tender.json#ocds-213czf-000-00001", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "date": { + "title": "Release Date", + "description": "The date of the release, should match `date` at the root level of the release. This is used to sort the releases in the list into date order.", + "type": "string", + "format": "date-time" + }, + "tag": { + "title": "Release Tag", + "description": "The tag should match the tag in the release. This provides additional context when reviewing a record to see what types of releases are included for this ocid.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "planning", + "tender", + "tenderAmendment", + "tenderUpdate", + "tenderCancellation", + "award", + "awardUpdate", + "awardCancellation", + "contract", + "contractUpdate", + "contractAmendment", + "implementation", + "implementationUpdate", + "contractTermination", + "compiled" + ] + } + } + }, + "required": [ + "url", + "date" + ] + }, + "minItems": 1 + }, + { + "title": "Embedded releases", + "description": "A list of releases, with all the data. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "$ref": "http://standard.open-contracting.org/schema/1__1__2/release-schema.json" + }, + "minItems": 1 + } + ] + }, + "compiledRelease": { + "title": "Compiled release", + "description": "This is the latest version of all the contracting data, it has the same schema as an open contracting release.", + "$ref": "http://standard.open-contracting.org/schema/1__1__2/release-schema.json" + }, + "versionedRelease": { + "title": "Versioned release", + "description": "This contains the history of the data in the compiledRecord. With all versions of the information and the release they came from.", + "$ref": "http://standard.open-contracting.org/schema/1__1__2/versioned-release-validation-schema.json" + } + }, + "required": [ + "ocid", + "releases" + ] + } + } +} \ No newline at end of file diff --git a/persistence-mongodb/src/main/resources/release-package-schema.json b/persistence-mongodb/src/main/resources/release-package-schema.json index 21cff5e09..c622e54b6 100644 --- a/persistence-mongodb/src/main/resources/release-package-schema.json +++ b/persistence-mongodb/src/main/resources/release-package-schema.json @@ -3,56 +3,106 @@ "title": "Schema for an Open Contracting Release Package", "description": "Note that all releases within a release package must have a unique releaseID within this release package.", "type": "object", - "required": ["uri", "publisher", "publishedDate", "releases"], + "required": [ + "uri", + "publisher", + "publishedDate", + "releases", + "version" + ], "properties": { "uri": { - "title": "Package Identifier", - "description": "The URI of this package that identifies it uniquely in the world.", + "title": "Package identifier", + "description": "The URI of this package that identifies it uniquely in the world. Recommended practice is to use a dereferenceable URI, where a persistent copy of this package is available.", "type": "string", "format": "uri" }, + "version": { + "title": "OCDS schema version", + "description": "The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "extensions": { + "title": "OCDS extensions", + "description": "An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for that extension.", + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, "publishedDate": { - "description": "The date that this package was published. Ideally this should be the latest date that there is release information in this package.", + "title": "Published date", + "description": "The date that this package was published. If this package is generated 'on demand', this date should reflect the date of the last change to the underlying contents of the package.", "type": "string", "format": "date-time" }, "releases": { + "title": "Releases", "type": "array", "minItems": 1, - "items": { "$ref": "http://ocds.open-contracting.org/standard/r/1__0__0/release-schema.json" }, + "items": { + "$ref": "http://standard.open-contracting.org/schema/1__1__2/release-schema.json" + }, "uniqueItems": true }, - "publisher": { + "publisher": { + "title": "Publisher", "description": "Information to uniquely identify the publisher of this package.", "type": "object", "properties": { "name": { + "title": "Name", + "description": "The name of the organization or department responsible for publishing this data.", "type": "string" }, "scheme": { + "title": "Scheme", "description": "The scheme that holds the unique identifiers used to identify the item being identified.", - "type": ["string", "null"], - "format": "uri" + "type": [ + "string", + "null" + ] }, "uid": { - "description": "The unique ID for this entity under the given ID scheme.", - "type": ["string", "null"] + "title": "uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": [ + "string", + "null" + ] }, "uri": { - "type": ["string", "null"], - "format" : "uri" + "title": "URI", + "description": "A URI to identify the publisher.", + "type": [ + "string", + "null" + ], + "format": "uri" } }, - "required": ["name"] + "required": [ + "name" + ] }, "license": { - "description": "A link to the license that applies to the data in this datapackage. A Public Domain Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", - "type": ["string", "null"], + "title": "License", + "description": "A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": [ + "string", + "null" + ], "format": "uri" }, "publicationPolicy": { - "description": "A link to a document describing the publishers [publication policy](http://ocds.open-contracting.org/standard/r/1__0__0/en/implementation/publication_patterns/#publication-policy).", - "type": ["string", "null"], + "title": "Publication policy", + "description": "A link to a document describing the publishers [publication policy](http://standard.open-contracting.org/latest/en/implementation/publication_policy/).", + "type": [ + "string", + "null" + ], "format": "uri" } } diff --git a/persistence-mongodb/src/main/resources/release-schema-all-required.json b/persistence-mongodb/src/main/resources/release-schema-all-required.json deleted file mode 100644 index acae98b55..000000000 --- a/persistence-mongodb/src/main/resources/release-schema-all-required.json +++ /dev/null @@ -1,1022 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Schema for an Open Contracting Release", - "type": "object", - "additionalProperties": false, - "properties": { - "ocid": { - "title": "Open Contracting ID", - "description": "A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier for the contracting process. For more information see the [Open Contracting Identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#ocid)", - "type": "string", - "mergeStrategy": "ocdsOmit" - }, - "id": { - "title": "Release ID", - "description": "A unique identifier that identifies this release. A releaseID must be unique within a release-package and must not contain the # character.", - "type": "string", - "mergeStrategy": "ocdsOmit" - }, - "date": { - "title": "Release Date", - "description": "The date this information is released, it may well be the same as the parent publishedDate, it must not be later than the publishedDate from the parent package. It is used to determine merge order.", - "type": "string", - "format": "date-time", - "mergeStrategy": "ocdsOmit" - }, - "tag": { - "title": "Release Tag", - "description": "A value from the [releaseTag codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#release-tag) that identifies the nature of the release being made. Tags may be used to filter release, or, in future, for for advanced validation when certain kinds of releases should contain certain fields.", - "type": "array", - "items": { - "type": "string", - "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] - }, - "mergeStrategy": "ocdsOmit" - }, - "initiationType": { - "title": "Initiation type", - "description": "String specifying the type of initiation process used for this contract, taken from the [initiationType](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#initiation-type) codelist. Currently only tender is supported.", - "type": "string", - "enum": ["tender"], - "mergeStrategy": "ocdsVersion" - }, - "planning": { - "title": "Planning", - "description": "Information from the planning phase of the contracting process. This includes information related to the process of deciding what to contract for, when and how.", - "$ref": "#/definitions/Planning" - }, - "tender": { - "title": "Tender", - "description": "The activities undertaken in order to enter into a contract.", - "$ref": "#/definitions/Tender" - }, - "buyer": { - "title": "Buyer", - "description": "The buyer is the entity whose budget will be used to purchase the goods. This may be different from the procuring agency who may be specified in the tender data.", - "$ref": "#/definitions/Organization" - }, - "awards": { - "title": "Awards", - "description": "Information from the award phase of the contracting process. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": {"idRef": "id"}, - "items": { "$ref": "#/definitions/Award" }, - "uniqueItems": true - }, - "contracts": { - "title": "Contracts", - "description": "Information from the contract creation phase of the procurement process.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": {"idRef": "id"}, - "items": {"$ref": "#/definitions/Contract" }, - "uniqueItems": true - }, - "language": { - "title": "Release language", - "description": "Specifies the default language of the data using either two-digit ISO 639-1, or extended BCP47 language tags. The use of two-letter codes from ISO 639-1 is strongly recommended.", - "type": ["string", "null"], - "default": "en", - "mergeStrategy": "ocdsVersion" - } - }, - "required": ["id", "ocid", "date", "tag", "initiationType", "planning", "tender", "buyer", "awards", "contracts", "language"], - "definitions": { - "Planning": { - "title": "Planning", - "description": "Information from the planning phase of the contracting process. Note that many other fields may be filled in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at this stage e.g. totalValue in tender", - "type": "object", - "required": ["budget", "rationale", "documents"], - "additionalProperties": false, - "properties": { - "budget": { "$ref": "#/definitions/Budget" }, - "rationale": { - "description": "The rationale for the procurement provided in free text. More detail can be provided in an attached document.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "documents": { - "description": "A list of documents related to the planning process.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" } - } - }, - "patternProperties": { - "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Tender": { - "title": "Tender", - "description": "Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.", - "type": "object", - "required": ["id", "title", "description", "status", "items", "minValue", "value", "procurementMethod", "procurementMethodRationale", "awardCriteria", "awardCriteriaDetails", "submissionMethod", "submissionMethodDetails", "tenderPeriod", "enquiryPeriod", "hasEnquiries", "eligibilityCriteria", "awardPeriod", "numberOfTenderers", "tenderers", "procuringEntity", "documents", "milestones", "amendment"], - "additionalProperties": false, - "properties": { - "id": { - "title": "Tender ID", - "description": "An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally held identifier for this tender.", - "type": ["string", "integer"], - "mergeStrategy": "ocdsVersion" - }, - "title": { - "description": "Tender title", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "Tender description", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "status": { - "title": "Tender Status", - "description": "The current status of the tender based on the [tenderStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#tender-status)", - "type": ["string", "null"], - "enum": ["planned", "active", "cancelled", "unsuccessful", "complete", null], - "mergeStrategy": "ocdsVersion" - }, - "items": { - "title": "Items to be procured", - "description": "The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Item" }, - "uniqueItems": true - }, - "minValue": { - "description": "The minimum estimated value of the procurement.", - "$ref": "#/definitions/Value" - }, - "value": { - "description": "The total upper estimated value of the procurement.", - "$ref": "#/definitions/Value" - }, - "procurementMethod": { - "description": "Specify tendering method against the [method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#method) as per [GPA definitions](http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm) of Open, Selective, Limited", - "type": ["string", "null"], - "enum": ["open", "selective", "limited", null], - "mergeStrategy": "ocdsVersion" - }, - "procurementMethodRationale": { - "description": "Rationale of procurement method, especially in the case of Limited tendering.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "awardCriteria": { - "description": "Specify the award criteria for the procurement, using the [award criteria codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#award-criteria)", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "awardCriteriaDetails": { - "description": "Any detailed or further information on the award or selection criteria.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "submissionMethod": { - "description": "Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the [submission method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#submission-method)", - "type": ["array", "null"], - "items": { - "type": "string" - }, - "mergeStrategy": "ocdsVersion" - }, - "submissionMethodDetails" : { - "description": "Any detailed or further information on the submission method. This may include the address, e-mail address or online service to which bids should be submitted, and any special requirements to be followed for submissions.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "tenderPeriod": { - "description": "The period when the tender is open for submissions. The end date is the closing date for tender submissions.", - "$ref": "#/definitions/Period" - }, - "enquiryPeriod": { - "description": "The period during which enquiries may be made and answered.", - "$ref": "#/definitions/Period" - }, - "hasEnquiries": { - "description": "A Yes/No field to indicate whether enquiries were part of tender process.", - "type": ["boolean", "null"], - "mergeStrategy": "ocdsVersion" - }, - "eligibilityCriteria": { - "description": "A description of any eligibility criteria for potential suppliers.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "awardPeriod": { - "description": "The date or period on which an award is anticipated to be made.", - "$ref": "#/definitions/Period" - }, - "numberOfTenderers": { - "definition": "The number of entities who submit a tender.", - "type": ["integer", "null"], - "mergeStrategy": "ocdsVersion" - }, - "tenderers": { - "description": "All entities who submit a tender.", - "type": "array", - "items": { "$ref": "#/definitions/Organization" }, - "uniqueItems": true, - "mergeStrategy": "ocdsVersion" - }, - "procuringEntity": { - "description": "The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.", - "$ref": "#/definitions/Organization" - }, - "documents": { - "description": "All documents and attachments related to the tender, including any notices. See the [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type) for details of potential documents to include.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" } - }, - "milestones": { - "description": "A list of milestones associated with the tender.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Milestone" } - }, - "amendment": { "$ref": "#/definitions/Amendment" } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(procurementMethodRationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(awardCriteriaDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(submissionMethodDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(eligibilityCriteria_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Award": { - "title": "Award", - "description": "An award for the given procurement. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", - "type": "object", - "required": ["id", "title", "description", "status", "date", "value", "suppliers", "items", "contractPeriod", "documents", "amendment"], - "additionalProperties": false, - "properties": { - "id": { - "title": "Award ID", - "description": "The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is part of (defined by a single ocid). See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) for further details.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "title": { - "description": "Award title", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "Award description", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "status": { - "title": "Award Status", - "description": "The current status of the award drawn from the [awardStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#award-status)", - "type": ["string", "null"], - "enum": ["pending", "active", "cancelled", "unsuccessful"], - "mergeStrategy": "ocdsVersion" - }, - "date": { - "title": "Award date", - "description": "The date of the contract award. This is usually the date on which a decision to award was made.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "value": { - "description": "The total value of this award. In the case of a framework contract this may be the total estimated lifetime value, or maximum value, of the agreement. There may be more than one award per procurement.", - "$ref": "#/definitions/Value" - }, - "suppliers": { - "description": "The suppliers awarded this award. If different suppliers have been awarded different items of values, these should be split into separate award blocks.", - "type": "array", - "items": { "$ref": "#/definitions/Organization" }, - "uniqueItems": true, - "mergeStrategy": "ocdsVersion" - }, - "items": { - "title": "Items Awarded", - "description": "The goods and services awarded in this award, broken into line items wherever possible. Items should not be duplicated, but the quantity specified instead.", - "type": "array", - "minItems": 1, - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Item" }, - "uniqueItems": true - }, - "contractPeriod": { - "description": "The period for which the contract has been awarded.", - "$ref": "#/definitions/Period" - }, - "documents": { - "description": "All documents and attachments related to the award, including any notices.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" }, - "uniqueItems": true - }, - "amendment": { - "$ref": "#/definitions/Amendment" - } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Contract": { - "type": "object", - "title": "Contract", - "description": "Information regarding the signed contract between the buyer and supplier(s).", - "required": ["id", "awardID", "title", "description", "status", "period", "value", "items", "dateSigned", "documents", "amendment", "implementation"], - "additionalProperties": false, - "properties": { - "id": { - "title": "Contract ID", - "description": "The identifier for this contract. It must be unique and cannot change within its Open Contracting Process (defined by a single ocid). See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) for further details.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "awardID": { - "title": "Award ID", - "description": "The award.id against which this contract is being issued.", - "type": ["string", "integer"], - "mergeStrategy": "ocdsVersion" - }, - "title": { - "description": "Contract title", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "Contract description", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "status": { - "title": "Contract Status", - "description": "The current status of the contract. Drawn from the [contractStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#contract-status)", - "type": ["string", "null"], - "enum": ["pending", "active", "cancelled", "terminated"], - "mergeStrategy": "ocdsVersion" - }, - "period": { - "description": "The start and end date for the contract.", - "$ref": "#/definitions/Period" - }, - "value": { - "description": "The total value of this contract.", - "$ref": "#/definitions/Value" - }, - "items": { - "title": "Items Contracted", - "description": "The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the award do not repeat.", - "type": "array", - "minItems": 1, - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Item" }, - "uniqueItems": true - }, - "dateSigned": { - "description": "The date the contract was signed. In the case of multiple signatures, the date of the last signature.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "documents": { - "description": "All documents and attachments related to the contract, including any notices.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" }, - "uniqueItems": true - }, - "amendment": { - "$ref": "#/definitions/Amendment" - }, - "implementation": { - "title": "Implementation", - "description": "Information related to the implementation of the contract in accordance with the obligations laid out therein.", - "$ref": "#/definitions/Implementation" - } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Implementation": { - "type": "object", - "title": "Implementation", - "description": "Information during the performance / implementation stage of the contract.", - "required": ["transactions", "milestones", "documents"], - "additionalProperties": false, - "properties": { - "transactions": { - "description": "A list of the spending transactions made against this contract", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Transaction" }, - "uniqueItems": true - }, - "milestones": { - "description": "As milestones are completed, milestone completions should be documented.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Milestone" }, - "uniqueItems": true - }, - "documents":{ - "description": "Documents and reports that are part of the implementation phase e.g. audit and evaluation reports.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" }, - "uniqueItems": true - } - } - }, - "Milestone": { - "type": "object", - "required": ["id", "title", "description", "dueDate", "dateModified", "status", "documents"], - "additionalProperties": false, - "properties": { - "id": { - "description": "A local identifier for this milestone, unique within this block. This field is used to keep track of multiple revisions of a milestone through the compilation from release to record mechanism.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "title": { - "description": "Milestone title", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "A description of the milestone.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "dueDate": { - "description": "The date the milestone is due.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "dateModified" : { - "description": "The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "status": { - "description": "The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#milestone-status).", - "type": ["string", "null"], - "enum": ["met", "notMet", "partiallyMet", null], - "mergeStrategy": "ocdsVersion" - }, - "documents": { - "description": "List of documents associated with this milestone.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" }, - "uniqueItems": true - } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Document": { - "type": "object", - "title": "Document", - "description": "Links to, or descriptions of, external documents can be attached at various locations within the standard. Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part of full open contracting.", - "required": ["id", "documentType", "title", "description", "url", "datePublished", "dateModified", "format", "language"], - "additionalProperties": false, - "properties": { - "id": { - "description": "A local, unique identifier for this document. This field is used to keep track of multiple revisions of a document through the compilation from release to record mechanism.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "documentType": { - "description": "A classification of the document described taken from the [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "title": { - "description": "The document title.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "A short description of the document. We recommend descriptions do not exceed 250 words. In the event the document is not accessible online, the description field can be used to describe arrangements for obtaining a copy of the document.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "url": { - "description": " direct link to the document or attachment. The server providing access to this document should be configured to correctly report the document mime type.", - "type": ["string", "null"], - "format": "uri", - "mergeStrategy": "ocdsVersion" - }, - "datePublished": { - "description": "The date on which the document was first published. This is particularly important for legally important documents such as notices of a tender.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "dateModified": { - "description": "Date that the document was last modified", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "format": { - "description": "The format of the document taken from the [IANA Media Types code list](http://www.iana.org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this document entry is being used to describe the offline publication of a document. Use values from the template column. Links to web pages should be tagged 'text/html'.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "language": { - "description": "Specifies the language of the linked document using either two-digit [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user need for distinguishing the language subtype.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Budget": { - "type": "object", - "title": "Budget Information", - "description": "This section contain information about the budget line, and associated projects, through which this contracting process is funded. It draws upon data model of the [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md), and should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a user to manually or automatically cross-reference with another published source of budget and project information.", - "mergeStrategy": "ocdsVersion", - "required": ["source", "id", "description", "amount", "project", "projectID", "uri"], - "additionalProperties": false, - "properties": { - "source": { - "title": "Data Source", - "description": "Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", - "type":["string", "null"], - "mergeStrategy": "ocdsVersion", - "format": "uri" - }, - "id":{ - "description": "An identifier for the budget line item which provides funds for this contracting process. This identifier should be possible to cross-reference against the provided data source.", - "mergeStrategy": "ocdsVersion", - "type":["string", "integer", "null"] - }, - "description": { - "title": "Budget Source", - "description": "A short free text description of the budget source. May be used to provide the title of the budget line, or the programme used to fund this project.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "amount": { - "description": "The value of the budget line item.", - "$ref": "#/definitions/Value" - }, - "project": { - "title": "Project Title", - "description": "The name of the project that through which this contracting process is funded (if applicable). Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. No translation option is offered for this string, as translated values can be provided in third-party data, linked from the data source above.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "projectID": { - "title": "Project Identifier", - "description": "An external identifier for the project that this contracting process forms part of, or is funded via (if applicable). Some organizations maintain a registry of projects, and the data should use the identifier from the relevant registry of projects.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "integer", "null"] - }, - "uri":{ - "title": "Linked budget information", - "description": "A URI pointing directly to a machine-readable record about the related budget or projects for this contracting process.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"], - "format": "uri" - } - }, - "patternProperties": { - "^(source_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(project_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Transaction": { - "type": "object", - "title": "Transaction Information", - "description": "A spending transaction related to the contracting process. Draws upon the data models of the [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md) and the [International Aid Transpareny Initiative](http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more detailed information held using a Budget Data Package, IATI file, or to provide enough information to allow a user to manually or automatically cross-reference with some other published source of transactional spending data.", - "required": ["id", "source", "date", "amount", "providerOrganization", "receiverOrganization", "uri"], - "additionalProperties": false, - "properties": { - "id":{ - "description": "A unique identifier for this transaction. This identifier should be possible to cross-reference against the provided data source. For the budget data package this is the id, for IATI, the transaction reference.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "source": { - "title": "Data Source", - "description": "Used to point either to a corresponding Budget Data Package, IATI file, or machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"], - "format": "uri" - }, - "date": { - "description": "The date of the transaction", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"], - "format": "date-time" - }, - "amount": { - "description": "The value of the transaction.", - "$ref": "#/definitions/Value" - }, - "providerOrganization":{ - "description": "The Organization Identifier for the organization from which the funds in this transaction originate. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", - "$ref": "#/definitions/Identifier" - }, - "receiverOrganization":{ - "description": "The Organization Identifier for the organization which receives the funds in this transaction. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", - "$ref": "#/definitions/Identifier" - }, - "uri":{ - "title":"Linked spending information", - "description":"A URI pointing directly to a machine-readable record about this spending transaction.", - "mergeStrategy": "ocdsVersion", - "type":["string", "null"], - "format":"uri" - } - } - }, - "Organization": { - "title": "Organization", - "description": "An organization.", - "type": "object", - "required": ["identifier", "additionalIdentifiers", "name", "address", "contactPoint"], - "additionalProperties": false, - "properties": { - "identifier": { - "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers) for the preferred scheme and identifier to use.", - "$ref": "#/definitions/Identifier" - }, - "additionalIdentifiers": { - "description": "A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier.", - "type": "array", - "mergeStrategy": "ocdsVersion", - "items": { "$ref": "#/definitions/Identifier" }, - "uniqueItems": true - }, - "name": { - "description": "The common name of the organization. The ID property provides an space for the formal legal name, and so this may either repeat that value, or could provide the common name by which this organization is known. This field could also include details of the department or sub-unit involved in this contracting process.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "address": { "$ref": "#/definitions/Address" }, - "contactPoint": { "$ref": "#/definitions/ContactPoint" } - }, - "patternProperties": { - "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Item": { - "type": "object", - "description": "A good, service, or work to be contracted.", - "required": ["id", "description", "classification", "additionalClassifications", "quantity", "unit"], - "additionalProperties": false, - "properties": { - "id": { - "description": "A local identifier to reference and merge the items by. Must be unique within a given array of items.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "description": { - "description": "A description of the goods, services to be provided.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "classification": { - "description": "The primary classification for the item. See the [itemClassificationScheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) to identify preferred classification lists, including CPV and GSIN.", - "$ref": "#/definitions/Classification" - }, - "additionalClassifications": { - "description": "An array of additional classifications for the item. See the [itemClassificationScheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) codelist for common options to use in OCDS. This may also be used to present codes from an internal classification scheme.", - "type": "array", - "mergeStrategy": "ocdsVersion", - "items": { "$ref": "#/definitions/Classification" }, - "uniqueItems": true - }, - "quantity": { - "description": "The number of units required", - "mergeStrategy": "ocdsVersion", - "minimum": 0, - "type": ["number", "null"] - }, - "unit": { - "description": "Description of the unit which the good comes in e.g. hours, kilograms. Made up of a unit name, and the value of a single unit.", - "type": "object", - "required": ["name", "value"], - "additionalProperties": false, - "properties": { - "name": { - "description": "Name of the unit", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "value": { - "description": "The monetary value of a single unit.", - "$ref": "#/definitions/Value" - } - }, - "patternProperties": { - "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - } - }, - "patternProperties": { - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Amendment": { - "type": "object", - "title": "Amendment information", - "required": ["date", "changes", "rationale"], - "additionalProperties": false, - "properties": { - "date": { - "title": "Amendment Date", - "description":"The data of this amendment.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "changes": { - "title": "Amended fields", - "description": "Comma-seperated list of affected fields.", - "mergeStrategy": "ocdsVersion", - "type": "array", - "required": ["property", "former_value"], - "additionalProperties": false, - "items": { - "type": "object", - "properties": { - "property": { - "description": "The property name that has been changed relative to the place the amendment is. For example if the contract value has changed, then the property under changes within the contract.amendment would be value.amount. ", - "type": "string" - }, - "former_value": { - "description": "The previous value of the changed property, in whatever type the property is.", - "type": ["string", "number", "integer", "array", "object", "null"] - } - } - } - }, - "rationale": { - "description": "An explanation for the amendment.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Classification": { - "type": "object", - "required": ["scheme", "id", "description", "uri"], - "additionalProperties": false, - "properties": { - "scheme": { - "description": "An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the scheme/codelist from which the classification is drawn. For line item classifications, this value should represent an known [Item Classification Scheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#item-classification-scheme) wherever possible.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "id": { - "description": "The classification code drawn from the selected scheme.", - "type": ["string", "integer", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "A textual description or title for the code.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "uri": { - "description": "A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme this value should be left blank.", - "type": ["string", "null"], - "format" : "uri", - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Identifier": { - "type": "object", - "required": ["scheme", "id", "legalName", "uri"], - "additionalProperties": false, - "properties": { - "scheme": { - "description": "Organization identifiers be drawn from an existing identification scheme. This field is used to indicate the scheme or codelist in which the identifier will be found. This value should be drawn from the [Organization Identifier Scheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#organization-identifier-scheme).", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "id": { - "description": "The identifier of the organization in the selected scheme.", - "type": ["string", "integer", "null"], - "mergeStrategy": "ocdsVersion" - }, - "legalName": { - "description": "The legally registered name of the organization.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "uri": { - "description": "A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the website of the organization: that can be done through the url field of the Organization contact point.", - "type": ["string", "null"], - "format" : "uri", - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(legalName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Address": { - "description": "An address. This may be the legally registered address of the organization, or may be a correspondence address for this particular contracting process.", - "type": "object", - "required": ["streetAddress", "locality", "region", "postalCode", "countryName"], - "additionalProperties": false, - "properties": { - "streetAddress": { - "type": ["string", "null"], - "description": "The street address. For example, 1600 Amphitheatre Pkwy.", - "mergeStrategy": "ocdsVersion" - }, - "locality":{ - "type": ["string", "null"], - "description": "The locality. For example, Mountain View.", - "mergeStrategy": "ocdsVersion" - }, - "region": { - "type": ["string", "null"], - "description":"The region. For example, CA.", - "mergeStrategy": "ocdsVersion" - }, - "postalCode": { - "type": ["string", "null"], - "description":"The postal code. For example, 94043.", - "mergeStrategy": "ocdsVersion" - }, - "countryName": { - "type": ["string", "null"], - "description":"The country name. For example, United States.", - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(countryName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "ContactPoint": { - "type": "object", - "description": "An person, contact point or department to contact in relation to this contracting process.", - "required": ["name", "email", "telephone", "faxNumber", "url"], - "additionalProperties": false, - "properties": { - "name": { - "type": ["string", "null"], - "description":"The name of the contact person, department, or contact point, for correspondence relating to this contracting process.", - "mergeStrategy": "ocdsVersion" - }, - "email":{ - "type": ["string", "null"], - "description":"The e-mail address of the contact point/person.", - "mergeStrategy": "ocdsVersion" - }, - "telephone": { - "type": ["string", "null"], - "description":"The telephone number of the contact point/person. This should include the international dialling code.", - "mergeStrategy": "ocdsVersion" - }, - "faxNumber": { - "type": ["string", "null"], - "description":"The fax number of the contact point/person. This should include the international dialling code.", - "mergeStrategy": "ocdsVersion" - }, - "url": { - "type": ["string", "null"], - "description":"A web address for the contact point/person.", - "format": "uri", - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "Value": { - "type": "object", - "required": ["amount", "currency"], - "additionalProperties": false, - "properties": { - "amount": { - "description": "Amount as a number.", - "type": ["number", "null"], - "minimum": 0, - "mergeStrategy": "ocdsVersion" - }, - "currency": { - "description": "The currency in 3-letter ISO 4217 format.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - } - } - }, - "Period": { - "type": "object", - "title": "Period", - "required": ["startDate", "endDate"], - "additionalProperties": false, - "properties": { - "startDate": { - "description": "The start date for the period.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "endDate": { - "description": "The end date for the period.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - } - } - } - } -} diff --git a/persistence-mongodb/src/main/resources/release-schema.json b/persistence-mongodb/src/main/resources/release-schema.json index 4fa58b106..76db1a3ed 100644 --- a/persistence-mongodb/src/main/resources/release-schema.json +++ b/persistence-mongodb/src/main/resources/release-schema.json @@ -1,1148 +1,2190 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Schema for an Open Contracting Release", - "type": "object", - "properties": { - "ocid": { - "title": "Open Contracting ID", - "description": "A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier for the contracting process. For more information see the [Open Contracting Identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#ocid)", - "type": "string", - "mergeStrategy": "ocdsOmit" + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier for the contracting process. For more information see the [Open Contracting Identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/)", + "type": "string", + "minLength": 1 + }, + "id": { + "title": "Release ID", + "description": "An identifier for this particular release of information. A release identifier must be unique within the scope of its related contracting process (defined by a common ocid), and unique within any release package it appears in. A release identifier must not contain the # character.", + "type": "string", + "minLength": 1, + "omitWhenMerged": true + }, + "date": { + "title": "Release Date", + "description": "The date this information was first released, or published.", + "type": "string", + "format": "date-time", + "omitWhenMerged": true + }, + "tag": { + "title": "Release Tag", + "description": "One or more values from the [releaseTag codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#release-tag). Tags may be used to filter release and to understand the kind of information that a release might contain.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "planning", + "planningUpdate", + "tender", + "tenderAmendment", + "tenderUpdate", + "tenderCancellation", + "award", + "awardUpdate", + "awardCancellation", + "contract", + "contractUpdate", + "contractAmendment", + "implementation", + "implementationUpdate", + "contractTermination", + "compiled" + ] + }, + "codelist": "releaseTag.csv", + "openCodelist": false, + "minItems": 1 + }, + "initiationType": { + "title": "Initiation type", + "description": "String specifying the type of initiation process used for this contract, taken from the [initiationType](http://standard.open-contracting.org/latest/en/schema/codelists/#initiation-type) codelist. Currently only tender is supported.", + "type": "string", + "enum": [ + "tender" + ], + "codelist": "initiationType.csv", + "openCodelist": false + }, + "parties": { + "title": "Parties", + "description": "Information on the parties (organizations, economic operators and other participants) who are involved in the contracting process and their roles, e.g. buyer, procuring entity, supplier etc. Organization references elsewhere in the schema are used to refer back to this entries in this list.", + "type": "array", + "items": { + "$ref": "#/definitions/Organization" + }, + "uniqueItems": true + }, + "buyer": { + "title": "Buyer", + "description": "The buyer is the entity whose budget will be used to purchase the goods. This may be different from the procuring entity who may be specified in the tender data.", + "$ref": "#/definitions/OrganizationReference" + }, + "planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. This includes information related to the process of deciding what to contract, when and how.", + "$ref": "#/definitions/Planning" + }, + "tender": { + "title": "Tender", + "description": "The activities undertaken in order to enter into a contract.", + "$ref": "#/definitions/Tender" + }, + "awards": { + "title": "Awards", + "description": "Information from the award phase of the contracting process. There may be more than one award per contracting process e.g. because the contract is split among different providers, or because it is a standing offer.", + "type": "array", + "items": { + "$ref": "#/definitions/Award" + }, + "uniqueItems": true + }, + "contracts": { + "title": "Contracts", + "description": "Information from the contract creation phase of the procurement process.", + "type": "array", + "items": { + "$ref": "#/definitions/Contract" + }, + "uniqueItems": true + }, + "language": { + "title": "Release language", + "description": "Specifies the default language of the data using either two-letter [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended.", + "type": [ + "string", + "null" + ], + "default": "en" + }, + "relatedProcesses": { + "uniqueItems": true, + "items": { + "$ref": "#/definitions/RelatedProcess" + }, + "description": "If this process follows on from one or more prior process, represented under a separate open contracting identifier (ocid) then details of the related process can be provided here. This is commonly used to relate mini-competitions to their parent frameworks, full tenders to a pre-qualification phase, or individual tenders to a broad planning process.", + "title": "Related processes", + "type": "array" + } + }, + "required": [ + "ocid", + "id", + "date", + "tag", + "initiationType" + ], + "definitions": { + "Planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. Note that many other fields may be filled in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at this stage e.g. totalValue in tender", + "type": "object", + "properties": { + "rationale": { + "title": "Rationale", + "description": "The rationale for the procurement provided in free text. More detail can be provided in an attached document.", + "type": [ + "string", + "null" + ] }, - "id": { - "title": "Release ID", - "description": "A unique identifier that identifies this release. A releaseID must be unique within a release-package and must not contain the # character.", - "type": "string", - "mergeStrategy": "ocdsOmit" + "budget": { + "$ref": "#/definitions/Budget" }, - "date": { - "title": "Release Date", - "description": "The date this information is released, it may well be the same as the parent publishedDate, it must not be later than the publishedDate from the parent package. It is used to determine merge order.", - "type": "string", - "format": "date-time", - "mergeStrategy": "ocdsOmit" - }, - "tag": { - "title": "Release Tag", - "description": "A value from the [releaseTag codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#release-tag) that identifies the nature of the release being made. Tags may be used to filter release, or, in future, for for advanced validation when certain kinds of releases should contain certain fields.", - "type": "array", - "items": { - "type": "string", - "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] - }, - "mergeStrategy": "ocdsOmit" - }, - "initiationType": { - "title": "Initiation type", - "description": "String specifying the type of initiation process used for this contract, taken from the [initiationType](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#initiation-type) codelist. Currently only tender is supported.", - "type": "string", - "enum": ["tender"], - "mergeStrategy": "ocdsVersion" - }, - "planning": { - "title": "Planning", - "description": "Information from the planning phase of the contracting process. This includes information related to the process of deciding what to contract for, when and how.", - "$ref": "#/definitions/Planning" - }, - "tender": { - "title": "Tender", - "description": "The activities undertaken in order to enter into a contract.", - "$ref": "#/definitions/Tender" - }, - "bids": { - "title": "Bids", - "description": "The bid section is used to publish summary statistics, and where applicable, individual bid information.", - "$ref": "#/definitions/Bids" - }, - "buyer": { - "title": "Buyer", - "description": "The buyer is the entity whose budget will be used to purchase the goods. This may be different from the procuring agency who may be specified in the tender data.", - "$ref": "#/definitions/Organization" - }, - "awards": { - "title": "Awards", - "description": "Information from the award phase of the contracting process. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": {"idRef": "id"}, - "items": { "$ref": "#/definitions/Award" }, - "uniqueItems": true - }, - "contracts": { - "title": "Contracts", - "description": "Information from the contract creation phase of the procurement process.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": {"idRef": "id"}, - "items": {"$ref": "#/definitions/Contract" }, - "uniqueItems": true + "documents": { + "title": "Documents", + "description": "A list of documents related to the planning process.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + } }, - "language": { - "title": "Release language", - "description": "Specifies the default language of the data using either two-digit ISO 639-1, or extended BCP47 language tags. The use of two-letter codes from ISO 639-1 is strongly recommended.", - "type": ["string", "null"], - "default": "en", - "mergeStrategy": "ocdsVersion" + "milestones": { + "title": "Planning milestones", + "description": "A list of milestones associated with the planning stage.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] } + } }, - "required": ["ocid", "id", "date", "tag", "initiationType"], - "definitions": { - "Planning": { - "title": "Planning", - "description": "Information from the planning phase of the contracting process. Note that many other fields may be filled in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at this stage e.g. totalValue in tender", - "type": "object", - "properties": { - "budget": { "$ref": "#/definitions/Budget" }, - "rationale": { - "description": "The rationale for the procurement provided in free text. More detail can be provided in an attached document.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "documents": { - "description": "A list of documents related to the planning process.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" } - } - }, - "patternProperties": { - "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "Tender": { + "title": "Tender", + "description": "Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "Tender ID", + "description": "An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally held identifier for this tender.", + "type": [ + "string", + "integer" + ], + "minLength": 1, + "versionId": true }, - "Tender": { - "title": "Tender", - "description": "Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.", - "type": "object", - "required": ["id"], - "properties": { - "id": { - "title": "Tender ID", - "description": "An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally held identifier for this tender.", - "type": ["string", "integer"], - "mergeStrategy": "ocdsVersion" - }, - "title": { - "description": "Tender title", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "Tender description", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "status": { - "title": "Tender Status", - "description": "The current status of the tender based on the [tenderStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#tender-status)", - "type": ["string", "null"], - "enum": ["planned", "active", "cancelled", "unsuccessful", "complete", null], - "mergeStrategy": "ocdsVersion" - }, - "items": { - "title": "Items to be procured", - "description": "The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Item" }, - "uniqueItems": true - }, - "minValue": { - "description": "The minimum estimated value of the procurement.", - "$ref": "#/definitions/Value" - }, - "value": { - "description": "The total upper estimated value of the procurement.", - "$ref": "#/definitions/Value" - }, - "procurementMethod": { - "description": "Specify tendering method against the [method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#method) as per [GPA definitions](http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm) of Open, Selective, Limited", - "type": ["string", "null"], - "enum": ["open", "selective", "limited", null], - "mergeStrategy": "ocdsVersion" - }, - "procurementMethodRationale": { - "description": "Rationale of procurement method, especially in the case of Limited tendering.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "awardCriteria": { - "description": "Specify the award criteria for the procurement, using the [award criteria codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#award-criteria)", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "awardCriteriaDetails": { - "description": "Any detailed or further information on the award or selection criteria.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "submissionMethod": { - "description": "Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the [submission method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#submission-method)", - "type": ["array", "null"], - "items": { - "type": "string" - }, - "mergeStrategy": "ocdsVersion" - }, - "submissionMethodDetails" : { - "description": "Any detailed or further information on the submission method. This may include the address, e-mail address or online service to which bids should be submitted, and any special requirements to be followed for submissions.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "tenderPeriod": { - "description": "The period when the tender is open for submissions. The end date is the closing date for tender submissions.", - "$ref": "#/definitions/Period" - }, - "enquiryPeriod": { - "description": "The period during which enquiries may be made and answered.", - "$ref": "#/definitions/Period" - }, - "hasEnquiries": { - "description": "A Yes/No field to indicate whether enquiries were part of tender process.", - "type": ["boolean", "null"], - "mergeStrategy": "ocdsVersion" - }, - "eligibilityCriteria": { - "description": "A description of any eligibility criteria for potential suppliers.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "awardPeriod": { - "description": "The date or period on which an award is anticipated to be made.", - "$ref": "#/definitions/Period" - }, - "numberOfTenderers": { - "definition": "The number of entities who submit a tender.", - "type": ["integer", "null"], - "mergeStrategy": "ocdsVersion" - }, - "tenderers": { - "description": "All entities who submit a tender.", - "type": "array", - "items": { "$ref": "#/definitions/Organization" }, - "uniqueItems": true, - "mergeStrategy": "ocdsVersion" - }, - "procuringEntity": { - "description": "The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.", - "$ref": "#/definitions/Organization" - }, - "documents": { - "description": "All documents and attachments related to the tender, including any notices. See the [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type) for details of potential documents to include.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" } - }, - "milestones": { - "description": "A list of milestones associated with the tender.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Milestone" } - }, - "amendment": { "$ref": "#/definitions/Amendment" } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(procurementMethodRationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(awardCriteriaDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(submissionMethodDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(eligibilityCriteria_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "title": { + "title": "Tender title", + "description": "A title for this tender. This will often be used by applications as a headline to attract interest, and to help analysts understand the nature of this procurement.", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Tender description", + "description": "A summary description of the tender. This should complement structured information provided using the items array. Descriptions should be short and easy to read. Avoid using ALL CAPS. ", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Tender status", + "description": "The current status of the tender based on the [tenderStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#tender-status)", + "type": [ + "string", + "null" + ], + "codelist": "tenderStatus.csv", + "openCodelist": false, + "enum": [ + "planning", + "planned", + "active", + "cancelled", + "unsuccessful", + "complete", + "withdrawn", + null + ] + }, + "procuringEntity": { + "title": "Procuring entity", + "description": "The entity managing the procurement. This may be different from the buyer who pays for, or uses, the items being procured.", + "$ref": "#/definitions/OrganizationReference" + }, + "items": { + "title": "Items to be procured", + "description": "The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.", + "type": "array", + "items": { + "$ref": "#/definitions/Item" + }, + "uniqueItems": true + }, + "value": { + "title": "Value", + "description": "The total upper estimated value of the procurement. A negative value indicates that the contracting process may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum estimated value of the procurement. A negative value indicates that the contracting process may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "procurementMethod": { + "title": "Procurement method", + "description": "Specify tendering method using the [method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#method). This is a closed codelist. Local method types should be mapped to this list.", + "type": [ + "string", + "null" + ], + "codelist": "method.csv", + "openCodelist": false, + "enum": [ + "open", + "selective", + "limited", + "direct", + null + ] + }, + "procurementMethodDetails": { + "title": "Procurement method details", + "description": "Additional detail on the procurement method used. This field may be used to provide the local name of the particular procurement method used.", + "type": [ + "string", + "null" + ] + }, + "procurementMethodRationale": { + "title": "Procurement method rationale", + "description": "Rationale for the chosen procurement method. This is especially important to provide a justification in the case of limited tenders or direct awards.", + "type": [ + "string", + "null" + ] }, - "BidsStatistic": { - "title": "Bid Statistic", - "description": "For reporting aggregate statistics about the bids related to a tender. Where lots are in use, statistics may optionally be broken down by lot. ", - "type": [ - "object", - "null" - ], - "required": [ - "id", - "measure", - "value" - ], - "properties": { - "id": { - "title": "ID", - "description": "An internal identifier for this statistical item.", + "mainProcurementCategory": { + "title": "Main procurement category", + "description": "The primary category describing the main object of this contracting process from the [procurementCategory](http://standard.open-contracting.org/latest/en/schema/codelists/#procurement-category) codelist. This is a closed codelist. Local classifications should be mapped to this list.", + "type": [ + "string", + "null" + ], + "codelist": "procurementCategory.csv", + "openCodelist": false, + "enum": [ + "goods", + "works", + "services", + null + ] + }, + "additionalProcurementCategories": { + "title": "Additional procurement categories", + "description": "Any additional categories which describe the objects of this contracting process, from the [extendedProcurementCategory](http://standard.open-contracting.org/latest/en/schema/codelists/#extended-procurement-category) codelist. This is an open codelist. Local categories can be included in this list.", + "type": [ + "array", + "null" + ], + "items": { "type": [ "string", "null" ] }, - "measure": { - "title": "Measure", - "description": "An item from the bidStatistics codelist for the statisic reported in value.", - "type": [ - "string", - "null" - ], - "codelist": "bidStatistics.csv", - "openCodelist": true + "codelist": "extendedProcurementCategory.csv", + "openCodelist": true + }, + "awardCriteria": { + "title": "Award criteria", + "description": "Specify the award criteria for the procurement, using the [award criteria codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#award-criteria)", + "type": [ + "string", + "null" + ], + "codelist": "awardCriteria.csv", + "openCodelist": true + }, + "awardCriteriaDetails": { + "title": "Award criteria details", + "description": "Any detailed or further information on the award or selection criteria.", + "type": [ + "string", + "null" + ] + }, + "submissionMethod": { + "title": "Submission method", + "description": "Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the [submission method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#submission-method)", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" }, - "date": { - "title": "Date", - "description": "The date when this statistic was last updated. This is often the closing date of the tender process. This field can be left blank unless either (a) the same statistic is provided from multiple points in time, or (b) there is a specific local requirement for the data when statistics were calculated to be provided.", - "type": [ - "string", - "null" - ], - "format": "date-time" + "codelist": "submissionMethod.csv", + "openCodelist": true + }, + "submissionMethodDetails": { + "title": "Submission method details", + "description": "Any detailed or further information on the submission method. This may include the address, e-mail address or online service to which bids should be submitted, and any special requirements to be followed for submissions.", + "type": [ + "string", + "null" + ] + }, + "tenderPeriod": { + "title": "Tender period", + "description": "The period when the tender is open for submissions. The end date is the closing date for tender submissions.", + "$ref": "#/definitions/Period" + }, + "enquiryPeriod": { + "title": "Enquiry period", + "description": "The period during which potential bidders may submit questions and requests for clarification to the entity managing procurement. Details of how to submit enquiries should be provided in attached notices, or in submissionMethodDetails. Structured dates for when responses to questions will be made can be provided using tender milestones.", + "$ref": "#/definitions/Period" + }, + "hasEnquiries": { + "title": "Has enquiries?", + "description": "A true/false field to indicate whether any enquiries were received during the tender process. Structured information on enquiries that were received, and responses to them, can be provided using the enquiries extension.", + "type": [ + "boolean", + "null" + ] + }, + "eligibilityCriteria": { + "title": "Eligibility criteria", + "description": "A description of any eligibility criteria for potential suppliers.", + "type": [ + "string", + "null" + ] + }, + "awardPeriod": { + "title": "Evaluation and award period", + "description": "The period for decision making regarding the contract award. The end date should be the date on which an award decision is due to be finalized. The start date is optional, and may be used to indicate the start of an evaluation period.", + "$ref": "#/definitions/Period" + }, + "contractPeriod": { + "description": "The period over which the contract is estimated or required to be active. If the tender does not specify explicit dates, the duration field may be used.", + "title": "Contract period", + "$ref": "#/definitions/Period" + }, + "numberOfTenderers": { + "title": "Number of tenderers", + "description": "The number of parties who submit a bid.", + "type": [ + "integer", + "null" + ] + }, + "tenderers": { + "title": "Tenderers", + "description": "All parties who submit a bid on a tender. More detailed information on bids and the bidding organization can be provided using the optional bid extension.", + "type": "array", + "items": { + "$ref": "#/definitions/OrganizationReference" }, - "value": { - "title": "Value", - "description": "The value for the measure in question. Total counts should be provided as an integer. Percentages should be presented as a proportion of 1 (e.g. 10% = 0.1)", - "type": [ - "number", - "null" - ] + "uniqueItems": true + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the tender, including any notices. See the [documentType codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#document-type) for details of potential documents to include. Common documents include official legal notices of tender, technical specifications, evaluation criteria, and, as a tender process progresses, clarifications and replies to queries.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + } + }, + "milestones": { + "title": "Milestones", + "description": "A list of milestones associated with the tender.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + }, + "amendments": { + "description": "A tender amendment is a formal change to the tender, and generally involves the publication of a new tender notice/release. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the amendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an amendments (plural) array.", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(procurementMethodRationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(awardCriteriaDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(submissionMethodDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(eligibilityCriteria_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Award": { + "title": "Award", + "description": "An award for the given procurement. There may be more than one award per contracting process e.g. because the contract is split among different providers, or because it is a standing offer.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "Award ID", + "description": "The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is part of (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for further details.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Title", + "description": "Award title", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Description", + "description": "Award description", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Award status", + "description": "The current status of the award drawn from the [awardStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#award-status)", + "type": [ + "string", + "null" + ], + "enum": [ + "pending", + "active", + "cancelled", + "unsuccessful", + null + ], + "codelist": "awardStatus.csv", + "openCodelist": false + }, + "date": { + "title": "Award date", + "description": "The date of the contract award. This is usually the date on which a decision to award was made.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "value": { + "title": "Value", + "description": "The total value of this award. In the case of a framework contract this may be the total estimated lifetime value, or maximum value, of the agreement. There may be more than one award per procurement. A negative value indicates that the award may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "suppliers": { + "title": "Suppliers", + "description": "The suppliers awarded this award. If different suppliers have been awarded different items of values, these should be split into separate award blocks.", + "type": "array", + "items": { + "$ref": "#/definitions/OrganizationReference" }, - "notes": { - "title": "Notes", - "description": "Any notes required to understand or interpret the given statistic.", - "type": [ - "string", - "null" - ] + "uniqueItems": true + }, + "items": { + "title": "Items awarded", + "description": "The goods and services awarded in this award, broken into line items wherever possible. Items should not be duplicated, but the quantity specified instead.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/Item" }, - "relatedLot": { - "title": "Related Lot", - "description": "Where lots are in use, if this statistic relates to bids on a particular lot, provide the lot identifier here. If left blank, the statistic will be interpreted as applying to the whole tender.", - "type": [ - "string", - "null" - ] + "uniqueItems": true + }, + "contractPeriod": { + "title": "Contract period", + "description": "The period for which the contract has been awarded.", + "$ref": "#/definitions/Period" + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the award, including any notices.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + }, + "amendments": { + "description": "An award amendment is a formal change to the details of the award, and generally involves the publication of a new award notice/release. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the amendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an amendments (plural) array.", + "deprecatedVersion": "1.1" } } }, - "Bids": { - "title": "Bids", - "description": "Summary and detailed information about bids received and evaluated as part of this contracting process.", - "type": "object", - "properties": { - "statistics": { - "title": "Statistics", - "description": "Summary statistics on the number and nature of bids received. Where information is provided on individual bids, these statistics should match those that can be calculated from the bid details array.", - "type": [ - "array" - ], - "items": { - "$ref": "#/definitions/BidsStatistic" - } + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Contract": { + "type": "object", + "title": "Contract", + "description": "Information regarding the signed contract between the buyer and supplier(s).", + "required": [ + "id", + "awardID" + ], + "properties": { + "id": { + "title": "Contract ID", + "description": "The identifier for this contract. It must be unique and cannot change within its Open Contracting Process (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for further details.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "awardID": { + "title": "Award ID", + "description": "The award.id against which this contract is being issued.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Contract title", + "description": "Contract title", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Contract description", + "description": "Contract description", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Contract status", + "description": "The current status of the contract. Drawn from the [contractStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#contract-status)", + "type": [ + "string", + "null" + ], + "enum": [ + "pending", + "active", + "cancelled", + "terminated", + null + ], + "codelist": "contractStatus.csv", + "openCodelist": false + }, + "period": { + "title": "Period", + "description": "The start and end date for the contract.", + "$ref": "#/definitions/Period" + }, + "value": { + "title": "Value", + "description": "The total value of this contract. A negative value indicates that the contract will involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "items": { + "title": "Items contracted", + "description": "The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the award do not repeat.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/Item" }, - "details": { - "title": "Bid details", - "description": "An array of bids, providing information on the bidders, and where applicable, bid status, bid values and related documents. The extent to which this information can be disclosed varies from jurisdiction to jurisdiction.", - "type": "array", - "required": [ - "id" - ], - "items": { - "$ref": "#/definitions/Bid" - } + "uniqueItems": true + }, + "dateSigned": { + "title": "Date signed", + "description": "The date the contract was signed. In the case of multiple signatures, the date of the last signature.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the contract, including any notices.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + }, + "implementation": { + "title": "Implementation", + "description": "Information related to the implementation of the contract in accordance with the obligations laid out therein.", + "$ref": "#/definitions/Implementation" + }, + "relatedProcesses": { + "uniqueItems": true, + "items": { + "$ref": "#/definitions/RelatedProcess" + }, + "description": "If this process is followed by one or more contracting processes, represented under a separate open contracting identifier (ocid) then details of the related process can be provided here. This is commonly used to point to subcontracts, or to renewal and replacement processes for this contract.", + "title": "Related processes", + "type": "array" + }, + "milestones": { + "title": "Contract milestones", + "description": "A list of milestones associated with the finalization of this contract.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + }, + "amendments": { + "description": "A contract amendment is a formal change to, or extension of, a contract, and generally involves the publication of a new contract notice/release, or some other documents detailing the change. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the amendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an amendments (plural) array.", + "deprecatedVersion": "1.1" } } }, - "Bid": { - "title": "Bid", - "description": "For representing a bid in response to the tender or qualification stage in this contracting process.", - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "title": "ID", - "description": "A local identifier for this bid", - "type": [ - "string" - ] + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Implementation": { + "type": "object", + "title": "Implementation", + "description": "Information during the performance / implementation stage of the contract.", + "properties": { + "transactions": { + "title": "Transactions", + "description": "A list of the spending transactions made against this contract", + "type": "array", + "items": { + "$ref": "#/definitions/Transaction" }, - "date": { - "title": "Date", - "description": "The date when this bid was received.", - "type": [ - "string", - "null" - ], - "format": "date-time" + "uniqueItems": true + }, + "milestones": { + "title": "Milestones", + "description": "As milestones are completed, milestone completions should be documented.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" }, - "status": { - "title": "Status", - "description": "The status of the bid, drawn from the bidStatus codelist", - "type": [ - "string", - "null" - ], - "codelist": "bidStatus.csv", - "openCodelist": false + "uniqueItems": true + }, + "documents": { + "title": "Documents", + "description": "Documents and reports that are part of the implementation phase e.g. audit and evaluation reports.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" }, - "tenderers": { - "title": "Tenderer", - "description": "The party, or parties, responsible for this bid. This should provide a name and identifier, cross-referenced to an entry in the parties array at the top level of the release.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Organization" - } + "uniqueItems": true + } + } + }, + "Milestone": { + "title": "Milestone", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local identifier for this milestone, unique within this block. This field is used to keep track of multiple revisions of a milestone through the compilation from release to record mechanism.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Title", + "description": "Milestone title", + "type": [ + "string", + "null" + ] + }, + "type": { + "title": "Milestone type", + "description": "The type of milestone, drawn from an extended [milestoneType codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#milestone-type).", + "type": [ + "string", + "null" + ], + "codelist": "milestoneType.csv", + "openCodelist": true + }, + "description": { + "title": "Description", + "description": "A description of the milestone.", + "type": [ + "string", + "null" + ] + }, + "code": { + "title": "Milestone code", + "description": "Milestone codes can be used to track specific events that take place for a particular kind of contracting process. For example, a code of 'approvalLetter' could be used to allow applications to understand this milestone represents the date an approvalLetter is due or signed. Milestone codes is an open codelist, and codes should be agreed among data producers and the applications using that data.", + "type": [ + "string", + "null" + ] + }, + "dueDate": { + "title": "Due date", + "description": "The date the milestone is due.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "dateMet": { + "format": "date-time", + "title": "Date met", + "description": "The date on which the milestone was met.", + "type": [ + "string", + "null" + ] + }, + "dateModified": { + "title": "Date modified", + "description": "The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "status": { + "title": "Status", + "description": "The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#milestone-status).", + "type": [ + "string", + "null" + ], + "enum": [ + "scheduled", + "met", + "notMet", + "partiallyMet", + null + ], + "codelist": "milestoneStatus.csv", + "openCodelist": false + }, + "documents": { + "title": "Documents", + "description": "List of documents associated with this milestone (Deprecated in 1.1).", + "type": "array", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "Inclusion of documents at the milestone level is now deprecated. Documentation should be attached in the tender, award, contract or implementation sections, and titles and descriptions used to highlight the related milestone. Publishers who wish to continue to provide documents at the milestone level should explicitly declare this by using the milestone documents extension." }, - "value": { - "title": "Value", - "description": "The total value of the bid.", - "$ref": "#/definitions/Value" + "items": { + "$ref": "#/definitions/Document" }, - "documents": { - "title": "Documents", - "description": "All documents and attachments related to the bid and its evaluation.", - "type": "array", - "items": { - "$ref": "#/definitions/Document" - }, - "uniqueItems": true + "uniqueItems": true + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Document": { + "type": "object", + "title": "Document", + "description": "Links to, or descriptions of, external documents can be attached at various locations within the standard. Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part of full open contracting.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local, unique identifier for this document. This field is used to keep track of multiple revisions of a document through the compilation from release to record mechanism.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "documentType": { + "title": "Document type", + "description": "A classification of the document described taken from the [documentType codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#document-type). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": [ + "string", + "null" + ], + "codelist": "documentType.csv", + "openCodelist": true + }, + "title": { + "title": "Title", + "description": "The document title.", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Description", + "description": "A short description of the document. We recommend descriptions do not exceed 250 words. In the event the document is not accessible online, the description field can be used to describe arrangements for obtaining a copy of the document.", + "type": [ + "string", + "null" + ] + }, + "url": { + "title": "URL", + "description": " direct link to the document or attachment. The server providing access to this document should be configured to correctly report the document mime type.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "datePublished": { + "title": "Date published", + "description": "The date on which the document was first published. This is particularly important for legally important documents such as notices of a tender.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "dateModified": { + "title": "Date modified", + "description": "Date that the document was last modified", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "format": { + "title": "Format", + "description": "The format of the document taken from the [IANA Media Types codelist](http://www.iana.org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this document entry is being used to describe the offline publication of a document. Use values from the template column. Links to web pages should be tagged 'text/html'.", + "type": [ + "string", + "null" + ] + }, + "language": { + "title": "Language", + "description": "Specifies the language of the linked document using either two-letter [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user need for distinguishing the language subtype.", + "type": [ + "string", + "null" + ] + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Budget": { + "type": "object", + "title": "Budget information", + "description": "This section contain information about the budget line, and associated projects, through which this contracting process is funded. It draws upon data model of the [Fiscal Data Package](http://fiscal.dataprotocols.org/), and should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a user to manually or automatically cross-reference with another published source of budget and project information.", + "properties": { + "id": { + "title": "ID", + "description": "An identifier for the budget line item which provides funds for this contracting process. This identifier should be possible to cross-reference against the provided data source.", + "type": [ + "string", + "integer", + "null" + ] + }, + "description": { + "title": "Budget Source", + "description": "A short free text description of the budget source. May be used to provide the title of the budget line, or the programme used to fund this project.", + "type": [ + "string", + "null" + ] + }, + "amount": { + "title": "Amount", + "description": "The value reserved in the budget for this contracting process. A negative value indicates anticipated income to the budget as a result of this contracting process, rather than expenditure. Where the budget is drawn from multiple sources, the budget breakdown extension can be used.", + "$ref": "#/definitions/Value" + }, + "project": { + "title": "Project title", + "description": "The name of the project that through which this contracting process is funded (if applicable). Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. No translation option is offered for this string, as translated values can be provided in third-party data, linked from the data source above.", + "type": [ + "string", + "null" + ] + }, + "projectID": { + "title": "Project identifier", + "description": "An external identifier for the project that this contracting process forms part of, or is funded via (if applicable). Some organizations maintain a registry of projects, and the data should use the identifier from the relevant registry of projects.", + "type": [ + "string", + "integer", + "null" + ] + }, + "uri": { + "title": "Linked budget information", + "description": "A URI pointing directly to a machine-readable record about the budget line-item or line-items that fund this contracting process. Information may be provided in a range of formats, including using IATI, the Open Fiscal Data Standard or any other standard which provides structured data on budget sources. Human readable documents can be included using the planning.documents block.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "source": { + "title": "Data Source", + "description": "(Deprecated in 1.1) Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type": [ + "string", + "null" + ], + "deprecated": { + "deprecatedVersion": "1.1", + "description": "The budget data source field was intended to link to machine-readable data about the budget for a contracting process, but has been widely mis-used to provide free-text descriptions of budget providers. As a result, it has been removed from version 1.1. budget/uri can be used to provide a link to machine-readable budget information, and budget/description can be used to provide human-readable information on the budget source." + }, + "format": "uri" + } + }, + "patternProperties": { + "^(source_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(project_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Transaction": { + "type": "object", + "title": "Transaction information", + "description": "A spending transaction related to the contracting process. Draws upon the data models of the [Fiscal Data Package](http://fiscal.dataprotocols.org/) and the [International Aid Transparency Initiative](http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more detailed information held using a Fiscal Data Package, IATI file, or to provide enough information to allow a user to manually or automatically cross-reference with some other published source of transactional spending data.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A unique identifier for this transaction. This identifier should be possible to cross-reference against the provided data source. For IATI this is the transaction reference.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "source": { + "title": "Data source", + "description": "Used to point either to a corresponding Fiscal Data Package, IATI file, or machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "date": { + "title": "Date", + "description": "The date of the transaction", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "value": { + "$ref": "#/definitions/Value", + "title": "Value", + "description": "The value of the transaction." + }, + "payer": { + "$ref": "#/definitions/OrganizationReference", + "title": "Payer", + "description": "An organization reference for the organization from which the funds in this transaction originate." + }, + "payee": { + "$ref": "#/definitions/OrganizationReference", + "title": "Payee", + "description": "An organization reference for the organization which receives the funds in this transaction." + }, + "uri": { + "title": "Linked spending information", + "description": "A URI pointing directly to a machine-readable record about this spending transaction.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "amount": { + "title": "Amount", + "description": "(Deprecated in 1.1. Use transaction.value instead) The value of the transaction. A negative value indicates a refund or correction.", + "$ref": "#/definitions/Value", + "deprecated": { + "description": "This field has been replaced by the `transaction.value` field for consistency with the use of value and amount elsewhere in the standard.", + "deprecatedVersion": "1.1" + } + }, + "providerOrganization": { + "title": "Provider organization", + "description": "(Deprecated in 1.1. Use transaction.payer instead.) The Organization Identifier for the organization from which the funds in this transaction originate. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier", + "deprecated": { + "description": "This field has been replaced by the `transaction.payer` field to resolve ambiguity arising from 'provider' being interpreted as relating to the goods or services procured rather than the flow of funds between the parties.", + "deprecatedVersion": "1.1" } + }, + "receiverOrganization": { + "title": "Receiver organization", + "description": "(Deprecated in 1.1. Use transaction.payee instead). The Organization Identifier for the organization which receives the funds in this transaction. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier", + "deprecated": { + "description": "This field has been replaced by the `transaction.payee` field to resolve ambiguity arising from 'receiver' being interpreted as relating to the goods or services procured rather than the flow of funds between the parties.", + "deprecatedVersion": "1.1" + } + } + } + }, + "OrganizationReference": { + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "description": "The name of the party being referenced. This must match the name of an entry in the parties section.", + "title": "Organization name", + "minLength": 1 + }, + "id": { + "type": [ + "string", + "integer" + ], + "description": "The id of the party being referenced. This must match the id of an entry in the parties section.", + "title": "Organization ID" + }, + "identifier": { + "title": "Primary identifier", + "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for the preferred scheme and identifier to use.", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organizations should be referenced by their identifier and name in a document, and detailed legal identifier information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Identifier" + }, + "address": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organizations should be referenced by their identifier and name in a document, and address information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Address", + "description": "(Deprecated outside the parties section)", + "title": "Address" + }, + "additionalIdentifiers": { + "type": "array", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organizations should be referenced by their identifier and name in a document, and additional identifiers for an organization should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "items": { + "$ref": "#/definitions/Identifier" + }, + "title": "Additional identifiers", + "uniqueItems": true, + "wholeListMerge": true, + "description": "(Deprecated outside the parties section) A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier." + }, + "contactPoint": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organizations should be referenced by their identifier and name in a document, and contact point information for an organization should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/ContactPoint", + "description": "(Deprecated outside the parties section)", + "title": "Contact point" } }, - "Award": { - "title": "Award", - "description": "An award for the given procurement. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", - "type": "object", - "required": ["id"], - "properties": { - "id": { - "title": "Award ID", - "description": "The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is part of (defined by a single ocid). See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) for further details.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "title": { - "description": "Award title", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "Award description", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "status": { - "title": "Award Status", - "description": "The current status of the award drawn from the [awardStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#award-status)", - "type": ["string", "null"], - "enum": ["pending", "active", "cancelled", "unsuccessful"], - "mergeStrategy": "ocdsVersion" - }, - "date": { - "title": "Award date", - "description": "The date of the contract award. This is usually the date on which a decision to award was made.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "value": { - "description": "The total value of this award. In the case of a framework contract this may be the total estimated lifetime value, or maximum value, of the agreement. There may be more than one award per procurement.", - "$ref": "#/definitions/Value" - }, - "suppliers": { - "description": "The suppliers awarded this award. If different suppliers have been awarded different items of values, these should be split into separate award blocks.", - "type": "array", - "items": { "$ref": "#/definitions/Organization" }, - "uniqueItems": true, - "mergeStrategy": "ocdsVersion" - }, - "items": { - "title": "Items Awarded", - "description": "The goods and services awarded in this award, broken into line items wherever possible. Items should not be duplicated, but the quantity specified instead.", - "type": "array", - "minItems": 1, - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Item" }, - "uniqueItems": true - }, - "contractPeriod": { - "description": "The period for which the contract has been awarded.", - "$ref": "#/definitions/Period" - }, - "documents": { - "description": "All documents and attachments related to the award, including any notices.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" }, - "uniqueItems": true - }, - "amendment": { - "$ref": "#/definitions/Amendment" - } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "required": [ + "name" + ], + "type": "object", + "description": "The id and name of the party being referenced. Used to cross-reference to the parties section", + "title": "Organization reference" + }, + "Organization": { + "title": "Organization", + "description": "A party (organization)", + "type": "object", + "properties": { + "name": { + "title": "Common name", + "description": "A common name for this organization or other participant in the contracting process. The identifier object provides an space for the formal legal name, and so this may either repeat that value, or could provide the common name by which this organization or entity is known. This field may also include details of the department or sub-unit involved in this contracting process.", + "type": [ + "string", + "null" + ] }, - "Contract": { - "type": "object", - "title": "Contract", - "description": "Information regarding the signed contract between the buyer and supplier(s).", - "required": ["id", "awardID"], - "properties": { - "id": { - "title": "Contract ID", - "description": "The identifier for this contract. It must be unique and cannot change within its Open Contracting Process (defined by a single ocid). See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) for further details.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "awardID": { - "title": "Award ID", - "description": "The award.id against which this contract is being issued.", - "type": ["string", "integer"], - "mergeStrategy": "ocdsVersion" - }, - "title": { - "description": "Contract title", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "Contract description", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "status": { - "title": "Contract Status", - "description": "The current status of the contract. Drawn from the [contractStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#contract-status)", - "type": ["string", "null"], - "enum": ["pending", "active", "cancelled", "terminated"], - "mergeStrategy": "ocdsVersion" - }, - "period": { - "description": "The start and end date for the contract.", - "$ref": "#/definitions/Period" - }, - "value": { - "description": "The total value of this contract.", - "$ref": "#/definitions/Value" - }, - "items": { - "title": "Items Contracted", - "description": "The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the award do not repeat.", - "type": "array", - "minItems": 1, - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Item" }, - "uniqueItems": true - }, - "dateSigned": { - "description": "The date the contract was signed. In the case of multiple signatures, the date of the last signature.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "documents": { - "description": "All documents and attachments related to the contract, including any notices.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" }, - "uniqueItems": true - }, - "amendment": { - "$ref": "#/definitions/Amendment" - }, - "implementation": { - "title": "Implementation", - "description": "Information related to the implementation of the contract in accordance with the obligations laid out therein.", - "$ref": "#/definitions/Implementation" - } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "id": { + "type": [ + "string" + ], + "description": "The ID used for cross-referencing to this party from other sections of the release. This field may be built with the following structure {identifier.scheme}-{identifier.id}(-{department-identifier}).", + "title": "Entity ID" }, - "Implementation": { - "type": "object", - "title": "Implementation", - "description": "Information during the performance / implementation stage of the contract.", - "properties": { - "transactions": { - "description": "A list of the spending transactions made against this contract", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Transaction" }, - "uniqueItems": true - }, - "milestones": { - "description": "As milestones are completed, milestone completions should be documented.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Milestone" }, - "uniqueItems": true - }, - "documents":{ - "description": "Documents and reports that are part of the implementation phase e.g. audit and evaluation reports.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" }, - "uniqueItems": true - } - } + "identifier": { + "title": "Primary identifier", + "description": "The primary identifier for this organization or participant. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for the preferred scheme and identifier to use.", + "$ref": "#/definitions/Identifier" }, - "Milestone": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "description": "A local identifier for this milestone, unique within this block. This field is used to keep track of multiple revisions of a milestone through the compilation from release to record mechanism.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "title": { - "description": "Milestone title", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "A description of the milestone.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "dueDate": { - "description": "The date the milestone is due.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "dateModified" : { - "description": "The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "status": { - "description": "The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#milestone-status).", - "type": ["string", "null"], - "enum": ["met", "notMet", "partiallyMet", null], - "mergeStrategy": "ocdsVersion" - }, - "documents": { - "description": "List of documents associated with this milestone.", - "type": "array", - "mergeStrategy": "arrayMergeById", - "mergeOptions": { "idRef": "id" }, - "items": { "$ref": "#/definitions/Document" }, - "uniqueItems": true - } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "additionalIdentifiers": { + "title": "Additional identifiers", + "description": "A list of additional / supplemental identifiers for the organization or participant, using the [organization identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier.", + "type": "array", + "items": { + "$ref": "#/definitions/Identifier" + }, + "uniqueItems": true, + "wholeListMerge": true }, - "Document": { - "type": "object", - "title": "Document", - "description": "Links to, or descriptions of, external documents can be attached at various locations within the standard. Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part of full open contracting.", - "required": ["id"], - "properties": { - "id": { - "description": "A local, unique identifier for this document. This field is used to keep track of multiple revisions of a document through the compilation from release to record mechanism.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "documentType": { - "description": "A classification of the document described taken from the [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "title": { - "description": "The document title.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "A short description of the document. We recommend descriptions do not exceed 250 words. In the event the document is not accessible online, the description field can be used to describe arrangements for obtaining a copy of the document.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "url": { - "description": " direct link to the document or attachment. The server providing access to this document should be configured to correctly report the document mime type.", - "type": ["string", "null"], - "format": "uri", - "mergeStrategy": "ocdsVersion" - }, - "datePublished": { - "description": "The date on which the document was first published. This is particularly important for legally important documents such as notices of a tender.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "dateModified": { - "description": "Date that the document was last modified", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "format": { - "description": "The format of the document taken from the [IANA Media Types code list](http://www.iana.org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this document entry is being used to describe the offline publication of a document. Use values from the template column. Links to web pages should be tagged 'text/html'.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "language": { - "description": "Specifies the language of the linked document using either two-digit [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user need for distinguishing the language subtype.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "address": { + "$ref": "#/definitions/Address" }, - "Budget": { - "type": "object", - "title": "Budget Information", - "description": "This section contain information about the budget line, and associated projects, through which this contracting process is funded. It draws upon data model of the [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md), and should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a user to manually or automatically cross-reference with another published source of budget and project information.", - "mergeStrategy": "ocdsVersion", - "properties": { - "source": { - "title": "Data Source", - "description": "Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", - "type":["string", "null"], - "mergeStrategy": "ocdsVersion", - "format": "uri" - }, - "id":{ - "description": "An identifier for the budget line item which provides funds for this contracting process. This identifier should be possible to cross-reference against the provided data source.", - "mergeStrategy": "ocdsVersion", - "type":["string", "integer", "null"] - }, - "description": { - "title": "Budget Source", - "description": "A short free text description of the budget source. May be used to provide the title of the budget line, or the programme used to fund this project.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "amount": { - "description": "The value of the budget line item.", - "$ref": "#/definitions/Value" - }, - "project": { - "title": "Project Title", - "description": "The name of the project that through which this contracting process is funded (if applicable). Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. No translation option is offered for this string, as translated values can be provided in third-party data, linked from the data source above.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "projectID": { - "title": "Project Identifier", - "description": "An external identifier for the project that this contracting process forms part of, or is funded via (if applicable). Some organizations maintain a registry of projects, and the data should use the identifier from the relevant registry of projects.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "integer", "null"] - }, - "uri":{ - "title": "Linked budget information", - "description": "A URI pointing directly to a machine-readable record about the related budget or projects for this contracting process.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"], - "format": "uri" - } - }, - "patternProperties": { - "^(source_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - }, - "^(project_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "contactPoint": { + "$ref": "#/definitions/ContactPoint" }, - "Transaction": { - "type": "object", - "title": "Transaction Information", - "description": "A spending transaction related to the contracting process. Draws upon the data models of the [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md) and the [International Aid Transpareny Initiative](http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more detailed information held using a Budget Data Package, IATI file, or to provide enough information to allow a user to manually or automatically cross-reference with some other published source of transactional spending data.", - "required": ["id"], - "properties": { - "id":{ - "description": "A unique identifier for this transaction. This identifier should be possible to cross-reference against the provided data source. For the budget data package this is the id, for IATI, the transaction reference.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "source": { - "title": "Data Source", - "description": "Used to point either to a corresponding Budget Data Package, IATI file, or machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"], - "format": "uri" - }, - "date": { - "description": "The date of the transaction", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"], - "format": "date-time" - }, - "amount": { - "description": "The value of the transaction.", - "$ref": "#/definitions/Value" - }, - "providerOrganization":{ - "description": "The Organization Identifier for the organization from which the funds in this transaction originate. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", - "$ref": "#/definitions/Identifier" - }, - "receiverOrganization":{ - "description": "The Organization Identifier for the organization which receives the funds in this transaction. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", - "$ref": "#/definitions/Identifier" - }, - "uri":{ - "title":"Linked spending information", - "description":"A URI pointing directly to a machine-readable record about this spending transaction.", - "mergeStrategy": "ocdsVersion", - "type":["string", "null"], - "format":"uri" - } - } + "roles": { + "title": "Party roles", + "description": "The party's role(s) in the contracting process. Role(s) should be taken from the [partyRole codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#party-role). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "codelist": "partyRole.csv", + "openCodelist": true }, - "Organization": { - "title": "Organization", - "description": "An organization.", - "type": "object", - "properties": { - "identifier": { - "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers) for the preferred scheme and identifier to use.", - "$ref": "#/definitions/Identifier" - }, - "additionalIdentifiers": { - "description": "A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier.", - "type": "array", - "mergeStrategy": "ocdsVersion", - "items": { "$ref": "#/definitions/Identifier" }, - "uniqueItems": true - }, - "name": { - "description": "The common name of the organization. The ID property provides an space for the formal legal name, and so this may either repeat that value, or could provide the common name by which this organization is known. This field could also include details of the department or sub-unit involved in this contracting process.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "address": { "$ref": "#/definitions/Address" }, - "contactPoint": { "$ref": "#/definitions/ContactPoint" } - }, - "patternProperties": { - "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "details": { + "type": [ + "object", + "null" + ], + "description": "Additional classification information about parties can be provided using partyDetail extensions that define particular properties and classification schemes. ", + "title": "Details" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Item": { + "title": "Item", + "type": "object", + "description": "A good, service, or work to be contracted.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local identifier to reference and merge the items by. Must be unique within a given array of items.", + "type": [ + "string", + "integer" + ], + "minLength": 1 }, - "Item": { - "type": "object", - "description": "A good, service, or work to be contracted.", - "required": ["id"], - "properties": { - "id": { - "description": "A local identifier to reference and merge the items by. Must be unique within a given array of items.", - "type": ["string", "integer"], - "mergeStrategy": "overwrite" - }, - "description": { - "description": "A description of the goods, services to be provided.", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "classification": { - "description": "The primary classification for the item. See the [itemClassificationScheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) to identify preferred classification lists, including CPV and GSIN.", - "$ref": "#/definitions/Classification" - }, - "additionalClassifications": { - "description": "An array of additional classifications for the item. See the [itemClassificationScheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) codelist for common options to use in OCDS. This may also be used to present codes from an internal classification scheme.", - "type": "array", - "mergeStrategy": "ocdsVersion", - "items": { "$ref": "#/definitions/Classification" }, - "uniqueItems": true - }, - "quantity": { - "description": "The number of units required", - "mergeStrategy": "ocdsVersion", - "minimum": 0, - "type": ["number", "null"] - }, - "unit": { - "description": "Description of the unit which the good comes in e.g. hours, kilograms. Made up of a unit name, and the value of a single unit.", - "type": "object", - "properties": { - "name": { - "description": "Name of the unit", - "mergeStrategy": "ocdsVersion", - "type": ["string", "null"] - }, - "value": { - "description": "The monetary value of a single unit.", - "$ref": "#/definitions/Value" - } - }, - "patternProperties": { - "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - } - }, - "patternProperties": { - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "description": { + "title": "Description", + "description": "A description of the goods, services to be provided.", + "type": [ + "string", + "null" + ] }, - "Amendment": { - "type": "object", - "title": "Amendment information", - "properties": { - "date": { - "title": "Amendment Date", - "description":"The data of this amendment.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "changes": { - "title": "Amended fields", - "description": "Comma-seperated list of affected fields.", - "mergeStrategy": "ocdsVersion", - "type": "array", - "items": { - "type": "object", - "properties": { - "property": { - "description": "The property name that has been changed relative to the place the amendment is. For example if the contract value has changed, then the property under changes within the contract.amendment would be value.amount. ", - "type": "string" - }, - "former_value": { - "description": "The previous value of the changed property, in whatever type the property is.", - "type": ["string", "number", "integer", "array", "object", "null"] - } - } - } - }, - "rationale": { - "description": "An explanation for the amendment.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "classification": { + "title": "Classification", + "description": "The primary classification for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) to identify preferred classification lists, including CPV and GSIN.", + "$ref": "#/definitions/Classification" }, - "Classification": { - "type": "object", - "properties": { - "scheme": { - "description": "An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the scheme/codelist from which the classification is drawn. For line item classifications, this value should represent an known [Item Classification Scheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#item-classification-scheme) wherever possible.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "id": { - "description": "The classification code drawn from the selected scheme.", - "type": ["string", "integer", "null"], - "mergeStrategy": "ocdsVersion" - }, - "description": { - "description": "A textual description or title for the code.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "uri": { - "description": "A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme this value should be left blank.", - "type": ["string", "null"], - "format" : "uri", - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "additionalClassifications": { + "title": "Additional classifications", + "description": "An array of additional classifications for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) codelist for common options to use in OCDS. This may also be used to present codes from an internal classification scheme.", + "type": "array", + "items": { + "$ref": "#/definitions/Classification" + }, + "uniqueItems": true, + "wholeListMerge": true }, - "Identifier": { - "type": "object", - "properties": { - "scheme": { - "description": "Organization identifiers be drawn from an existing identification scheme. This field is used to indicate the scheme or codelist in which the identifier will be found. This value should be drawn from the [Organization Identifier Scheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#organization-identifier-scheme).", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "id": { - "description": "The identifier of the organization in the selected scheme.", - "type": ["string", "integer", "null"], - "mergeStrategy": "ocdsVersion" - }, - "legalName": { - "description": "The legally registered name of the organization.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - }, - "uri": { - "description": "A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the website of the organization: that can be done through the url field of the Organization contact point.", - "type": ["string", "null"], - "format" : "uri", - "mergeStrategy": "ocdsVersion" - } - }, - "patternProperties": { - "^(legalName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } + "quantity": { + "title": "Quantity", + "description": "The number of units required", + "type": [ + "number", + "null" + ] }, - "Address": { - "description": "An address. This may be the legally registered address of the organization, or may be a correspondence address for this particular contracting process.", - "type": "object", - "properties": { - "streetAddress": { - "type": ["string", "null"], - "description": "The street address. For example, 1600 Amphitheatre Pkwy.", - "mergeStrategy": "ocdsVersion" - }, - "locality":{ - "type": ["string", "null"], - "description": "The locality. For example, Mountain View.", - "mergeStrategy": "ocdsVersion" - }, - "region": { - "type": ["string", "null"], - "description":"The region. For example, CA.", - "mergeStrategy": "ocdsVersion" - }, - "postalCode": { - "type": ["string", "null"], - "description":"The postal code. For example, 94043.", - "mergeStrategy": "ocdsVersion" - }, - "countryName": { - "type": ["string", "null"], - "description":"The country name. For example, United States.", - "mergeStrategy": "ocdsVersion" - } + "unit": { + "title": "Unit", + "description": "A description of the unit in which the supplies, services or works are provided (e.g. hours, kilograms) and the unit-price. For comparability, an established list of units can be used. ", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "The list from which units of measure identifiers are taken. This should be an entry from the options available in the [unitClassificationScheme](http://standard.open-contracting.org/latest/en/schema/codelists/#unit-classification-scheme) codelist. Use of the scheme 'UNCEFACT' for the UN/CEFACT Recommendation 20 list of 'Codes for Units of Measure Used in International Trade' is recommended, although other options are available.", + "type": [ + "string", + "null" + ], + "codelist": "unitClassificationScheme.csv", + "openCodelist": true }, - "patternProperties": { - "^(countryName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } - } - }, - "ContactPoint": { - "type": "object", - "description": "An person, contact point or department to contact in relation to this contracting process.", - "properties": { - "name": { - "type": ["string", "null"], - "description":"The name of the contact person, department, or contact point, for correspondence relating to this contracting process.", - "mergeStrategy": "ocdsVersion" - }, - "email":{ - "type": ["string", "null"], - "description":"The e-mail address of the contact point/person.", - "mergeStrategy": "ocdsVersion" - }, - "telephone": { - "type": ["string", "null"], - "description":"The telephone number of the contact point/person. This should include the international dialling code.", - "mergeStrategy": "ocdsVersion" - }, - "faxNumber": { - "type": ["string", "null"], - "description":"The fax number of the contact point/person. This should include the international dialling code.", - "mergeStrategy": "ocdsVersion" - }, - "url": { - "type": ["string", "null"], - "description":"A web address for the contact point/person.", - "format": "uri", - "mergeStrategy": "ocdsVersion" - } + "id": { + "title": "ID", + "description": "The identifier from the codelist referenced in the scheme property. Check the codelist for details of how to find and use identifiers from the scheme in use.", + "type": [ + "string", + "null" + ], + "versionId": true }, - "patternProperties": { - "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { - "type": ["string", "null"] - } + "name": { + "title": "Name", + "description": "Name of the unit.", + "type": [ + "string", + "null" + ] + }, + "value": { + "title": "Value", + "description": "The monetary value of a single unit.", + "$ref": "#/definitions/Value" + }, + "uri": { + "title": "URI", + "description": "If the scheme used provide a machine-readable URI for this unit of measure, this can be given.", + "format": "uri", + "type": [ + "string", + "null" + ] } - }, - "Value": { - "type": "object", - "properties": { - "amount": { - "description": "Amount as a number.", - "type": ["number", "null"], - "minimum": 0, - "mergeStrategy": "ocdsVersion" - }, - "currency": { - "description": "The currency in 3-letter ISO 4217 format.", - "type": ["string", "null"], - "mergeStrategy": "ocdsVersion" - } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] } + } + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Amendment": { + "title": "Amendment", + "type": "object", + "description": "Amendment information", + "properties": { + "date": { + "title": "Amendment date", + "description": "The date of this amendment.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "rationale": { + "title": "Rationale", + "description": "An explanation for the amendment.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "An identifier for this amendment: often the amendment number", + "type": [ + "string", + "null" + ], + "title": "ID" + }, + "description": { + "description": "A free text, or semi-structured, description of the changes made in this amendment.", + "type": [ + "string", + "null" + ], + "title": "Description" }, - "Period": { + "amendsReleaseID": { + "description": "Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the values for this contracting process **before** the amendment was made.", + "type": [ + "string", + "null" + ], + "title": "Amended release (identifier)" + }, + "releaseID": { + "description": "Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the values for this contracting process **after** the amendment was made.", + "type": [ + "string", + "null" + ], + "title": "Amending release (identifier)" + }, + "changes": { + "title": "Amended fields", + "description": "An array change objects describing the fields changed, and their former values. (Deprecated in 1.1)", + "type": "array", + "items": { "type": "object", - "title": "Period", "properties": { - "startDate": { - "description": "The start date for the period.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - }, - "endDate": { - "description": "The end date for the period.", - "type": ["string", "null"], - "format": "date-time", - "mergeStrategy": "ocdsVersion" - } + "property": { + "title": "Property", + "description": "The property name that has been changed relative to the place the amendment is. For example if the contract value has changed, then the property under changes within the contract.amendment would be value.amount. (Deprecated in 1.1)", + "type": "string" + }, + "former_value": { + "title": "Former Value", + "description": "The previous value of the changed property, in whatever type the property is. (Deprecated in 1.1)", + "type": [ + "string", + "number", + "integer", + "array", + "object", + "null" + ] + } } + }, + "deprecated": { + "description": "A free-text or semi-structured string describing the changes made in each amendment can be provided in the amendment.description field. To provide structured information on the fields that have changed, publishers should provide releases indicating the state of the contracting process before and after the amendment. ", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Classification": { + "title": "Classification", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the scheme/codelist from which the classification is drawn. For line item classifications, this value should represent an known [Item Classification Scheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) wherever possible.", + "type": [ + "string", + "null" + ], + "codelist": "itemClassificationScheme.csv", + "openCodelist": true + }, + "id": { + "title": "ID", + "description": "The classification code drawn from the selected scheme.", + "type": [ + "string", + "integer", + "null" + ] + }, + "description": { + "title": "Description", + "description": "A textual description or title for the code.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme this value should be left blank.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Identifier": { + "title": "Identifier", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "Organization identifiers should be drawn from an existing organization identifier list. The scheme field is used to indicate the list or register from which the identifier is drawn. This value should be drawn from the [Organization Identifier Scheme](http://standard.open-contracting.org/latest/en/schema/codelists/#organization-identifier-scheme) codelist.", + "type": [ + "string", + "null" + ] + }, + "id": { + "title": "ID", + "description": "The identifier of the organization in the selected scheme.", + "type": [ + "string", + "integer", + "null" + ] + }, + "legalName": { + "title": "Legal Name", + "description": "The legally registered name of the organization.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the website of the organization: that can be done through the URL field of the Organization contact point.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "patternProperties": { + "^(legalName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Address": { + "title": "Address", + "description": "An address. This may be the legally registered address of the organization, or may be a correspondence address for this particular contracting process.", + "type": "object", + "properties": { + "streetAddress": { + "title": "Street address", + "type": [ + "string", + "null" + ], + "description": "The street address. For example, 1600 Amphitheatre Pkwy." + }, + "locality": { + "title": "Locality", + "type": [ + "string", + "null" + ], + "description": "The locality. For example, Mountain View." + }, + "region": { + "title": "Region", + "type": [ + "string", + "null" + ], + "description": "The region. For example, CA." + }, + "postalCode": { + "title": "Postal code", + "type": [ + "string", + "null" + ], + "description": "The postal code. For example, 94043." + }, + "countryName": { + "title": "Country name", + "type": [ + "string", + "null" + ], + "description": "The country name. For example, United States." + } + }, + "patternProperties": { + "^(countryName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "ContactPoint": { + "title": "Contact point", + "type": "object", + "description": "An person, contact point or department to contact in relation to this contracting process.", + "properties": { + "name": { + "title": "Name", + "type": [ + "string", + "null" + ], + "description": "The name of the contact person, department, or contact point, for correspondence relating to this contracting process." + }, + "email": { + "title": "Email", + "type": [ + "string", + "null" + ], + "description": "The e-mail address of the contact point/person." + }, + "telephone": { + "title": "Telephone", + "type": [ + "string", + "null" + ], + "description": "The telephone number of the contact point/person. This should include the international dialing code." + }, + "faxNumber": { + "title": "Fax number", + "type": [ + "string", + "null" + ], + "description": "The fax number of the contact point/person. This should include the international dialing code." + }, + "url": { + "title": "URL", + "type": [ + "string", + "null" + ], + "description": "A web address for the contact point/person.", + "format": "uri" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Value": { + "title": "Value", + "type": "object", + "properties": { + "amount": { + "title": "Amount", + "description": "Amount as a number.", + "type": [ + "number", + "null" + ] + }, + "currency": { + "title": "Currency", + "description": "The currency for each amount should always be specified using the uppercase 3-letter currency code from ISO4217.", + "type": [ + "string", + "null" + ], + "codelist": "currency.csv", + "openCodelist": false, + "enum": [ + "ADP", + "AED", + "AFA", + "AFN", + "ALK", + "ALL", + "AMD", + "ANG", + "AOA", + "AOK", + "AON", + "AOR", + "ARA", + "ARP", + "ARS", + "ARY", + "ATS", + "AUD", + "AWG", + "AYM", + "AZM", + "AZN", + "BAD", + "BAM", + "BBD", + "BDT", + "BEC", + "BEF", + "BEL", + "BGJ", + "BGK", + "BGL", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BOP", + "BOV", + "BRB", + "BRC", + "BRE", + "BRL", + "BRN", + "BRR", + "BSD", + "BTN", + "BUK", + "BWP", + "BYB", + "BYN", + "BYR", + "BZD", + "CAD", + "CDF", + "CHC", + "CHE", + "CHF", + "CHW", + "CLF", + "CLP", + "CNY", + "COP", + "COU", + "CRC", + "CSD", + "CSJ", + "CSK", + "CUC", + "CUP", + "CVE", + "CYP", + "CZK", + "DDM", + "DEM", + "DJF", + "DKK", + "DOP", + "DZD", + "ECS", + "ECV", + "EEK", + "EGP", + "ERN", + "ESA", + "ESB", + "ESP", + "ETB", + "EUR", + "FIM", + "FJD", + "FKP", + "FRF", + "GBP", + "GEK", + "GEL", + "GHC", + "GHP", + "GHS", + "GIP", + "GMD", + "GNE", + "GNF", + "GNS", + "GQE", + "GRD", + "GTQ", + "GWE", + "GWP", + "GYD", + "HKD", + "HNL", + "HRD", + "HRK", + "HTG", + "HUF", + "IDR", + "IEP", + "ILP", + "ILR", + "ILS", + "INR", + "IQD", + "IRR", + "ISJ", + "ISK", + "ITL", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAJ", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LSM", + "LTL", + "LTT", + "LUC", + "LUF", + "LUL", + "LVL", + "LVR", + "LYD", + "MAD", + "MDL", + "MGA", + "MGF", + "MKD", + "MLF", + "MMK", + "MNT", + "MOP", + "MRO", + "MTL", + "MTP", + "MUR", + "MVQ", + "MVR", + "MWK", + "MXN", + "MXP", + "MXV", + "MYR", + "MZE", + "MZM", + "MZN", + "NAD", + "NGN", + "NIC", + "NIO", + "NLG", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEH", + "PEI", + "PEN", + "PES", + "PGK", + "PHP", + "PKR", + "PLN", + "PLZ", + "PTE", + "PYG", + "QAR", + "RHD", + "ROK", + "ROL", + "RON", + "RSD", + "RUB", + "RUR", + "RWF", + "SAR", + "SBD", + "SCR", + "SDD", + "SDG", + "SDP", + "SEK", + "SGD", + "SHP", + "SIT", + "SKK", + "SLL", + "SOS", + "SRD", + "SRG", + "SSP", + "STD", + "SUR", + "SVC", + "SYP", + "SZL", + "THB", + "TJR", + "TJS", + "TMM", + "TMT", + "TND", + "TOP", + "TPE", + "TRL", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UAK", + "UGS", + "UGW", + "UGX", + "USD", + "USN", + "USS", + "UYI", + "UYN", + "UYP", + "UYU", + "UZS", + "VEB", + "VEF", + "VNC", + "VND", + "VUV", + "WST", + "XAF", + "XAG", + "XAU", + "XBA", + "XBB", + "XBC", + "XBD", + "XCD", + "XDR", + "XEU", + "XFO", + "XFU", + "XOF", + "XPD", + "XPF", + "XPT", + "XRE", + "XSU", + "XTS", + "XUA", + "XXX", + "YDD", + "YER", + "YUD", + "YUM", + "YUN", + "ZAL", + "ZAR", + "ZMK", + "ZMW", + "ZRN", + "ZRZ", + "ZWC", + "ZWD", + "ZWL", + "ZWN", + "ZWR", + null + ] + } + } + }, + "Period": { + "title": "Period", + "description": " ", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "The start date for the period. When known, a precise start date must always be provided.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "The end date for the period. When known, a precise end date must always be provided.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "maxExtentDate": { + "description": "The period cannot be extended beyond this date. This field is optional, and can be used to express the maximum available data for extension or renewal of this period.", + "format": "date-time", + "title": "Maximum extent", + "type": [ + "string", + "null" + ] + }, + "durationInDays": { + "description": "The maximum duration of this period in days. A user interface may wish to collect or display this data in months or years as appropriate, but should convert it into days when completing this field. This field can be used when exact dates are not known. Where a startDate and endDate are given, this field is optional, and should reflect the difference between those two days. Where a startDate and maxExtentDate are given, this field is optional, and should reflect the difference between startDate and maxExtentDate.", + "title": "Duration (days)", + "type": [ + "integer", + "null" + ] + } + } + }, + "RelatedProcess": { + "description": "A reference to a related contracting process: generally one preceding or following on from the current process.", + "type": "object", + "title": "Related Process", + "properties": { + "id": { + "title": "Relationship ID", + "description": "A local identifier for this relationship, unique within this array.", + "type": [ + "string" + ] + }, + "relationship": { + "items": { + "type": "string" + }, + "description": "Specify the type of relationship using the [related process codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#related-process).", + "title": "Relationship", + "type": [ + "array", + "null" + ], + "codelist": "relatedProcess.csv", + "openCodelist": true + }, + "title": { + "description": "The title of the related process, where referencing an open contracting process, this field should match the tender/title field in the related process.", + "title": "Related process title", + "type": [ + "string", + "null" + ] + }, + "scheme": { + "title": "Scheme", + "description": "The identification scheme used by this cross-reference from the [related process scheme codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#related-process-scheme) codelist. When cross-referencing information also published using OCDS, an Open Contracting ID (ocid) should be used.", + "type": [ + "string", + "null" + ], + "codelist": "relatedProcessScheme.csv", + "openCodelist": true + }, + "identifier": { + "description": "The identifier of the related process. When cross-referencing information also published using OCDS, this should be the Open Contracting ID (ocid).", + "title": "Identifier", + "type": [ + "string", + "null" + ] + }, + "uri": { + "format": "uri", + "description": "A URI pointing to a machine-readable document, release or record package containing the identified related process.", + "title": "Related process URI", + "type": [ + "string", + "null" + ] } + } } -} + } +} \ No newline at end of file diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/excel/ReleaseExportFileTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/excel/ReleaseExportFileTest.java index b4ca3cc8f..af7b26dd6 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/excel/ReleaseExportFileTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/excel/ReleaseExportFileTest.java @@ -25,9 +25,9 @@ public void createWorkbook() throws Exception { final ExcelFile releaseExcelFile = new ReleaseExportFile(new ArrayList<>(releasePackage.getReleases())); final Workbook workbook = releaseExcelFile.createWorkbook(); - // try (FileOutputStream outputStream = new FileOutputStream("/Users/ionut/Downloads/ocds-export.xlsx")) { - // workbook.write(outputStream); - // } +// try (FileOutputStream outputStream = new FileOutputStream("/home/mihai/ocds-export.xlsx")) { +// workbook.write(outputStream); +// } Assert.assertEquals("Number of sheets", 6, workbook.getNumberOfSheets()); @@ -52,7 +52,7 @@ public void createWorkbook() throws Exception { Assert.assertEquals("item number of rows", 9, workbook.getSheet("item").getLastRowNum()); - Assert.assertEquals("contract flatten organizatio id", "E09000003 | E09000003", + Assert.assertEquals("contract flatten organization id", "E09000003 | E09000003", workbook.getSheet("contract").getRow(2).getCell(17).toString()); } } diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImportTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImportTest.java index 4d1de5485..53391d066 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImportTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImportTest.java @@ -2,7 +2,7 @@ import org.apache.commons.digester3.binder.AbstractRulesModule; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.After; import org.junit.Assert; diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/MongoTemplateTestConfig.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/MongoTemplateTestConfig.java new file mode 100644 index 000000000..9e594105c --- /dev/null +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/MongoTemplateTestConfig.java @@ -0,0 +1,147 @@ +package org.devgateway.ocds.persistence.mongo.spring; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientOptions; +import de.flapdoodle.embed.mongo.MongodExecutable; +import de.flapdoodle.embed.mongo.MongodProcess; +import de.flapdoodle.embed.mongo.MongodStarter; +import de.flapdoodle.embed.mongo.config.IMongodConfig; +import de.flapdoodle.embed.mongo.config.MongoCmdOptionsBuilder; +import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; +import de.flapdoodle.embed.mongo.config.Net; +import de.flapdoodle.embed.mongo.distribution.Feature; +import de.flapdoodle.embed.mongo.distribution.IFeatureAwareVersion; +import org.devgateway.toolkit.persistence.mongo.spring.MongoTemplateConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.mongo.MongoProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoDbFactory; +import org.springframework.data.mongodb.core.convert.CustomConversions; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.util.EnumSet; + +/** + * Created by mpostelnicu on 6/12/17. + */ +@Configuration +@Profile("integration") +public class MongoTemplateTestConfig { + + public enum OCEMongoVersion implements IFeatureAwareVersion { + + V3_4_11("3.4.11", Feature.SYNC_DELAY, Feature.STORAGE_ENGINE); + + private final String specificVersion; + private EnumSet features; + + OCEMongoVersion(String vName, Feature... features) { + this.specificVersion = vName; + this.features = Feature.asSet(features); + } + + @Override + public String asInDownloadPath() { + return specificVersion; + } + + @Override + public boolean enabled(Feature feature) { + return features.contains(feature); + } + + @Override + public String toString() { + return "Version{" + specificVersion + '}'; + } + + } + + + @Autowired + private MongoProperties properties; + + @Autowired + private CustomConversions customConversions; + + @Autowired + private Environment environment; + + @Autowired(required = false) + private MongoClientOptions options; + + private String originalUri; + + @Bean(destroyMethod = "stop") + public MongodProcess mongodProcess(MongodExecutable embeddedMongoServer) throws IOException { + return embeddedMongoServer.start(); + } + + @Bean(destroyMethod = "stop") + public MongodExecutable embeddedMongoServer(MongodStarter mongodStarter, IMongodConfig iMongodConfig) + throws IOException { + return mongodStarter.prepare(iMongodConfig); + } + + @Bean + public IMongodConfig mongodConfig() throws IOException { + return new MongodConfigBuilder().version(OCEMongoVersion.V3_4_11) + .cmdOptions(new MongoCmdOptionsBuilder().useNoJournal(true) + .build()) + .build(); + } + + @Bean + public MongodStarter mongodStarter() { + return MongodStarter.getDefaultInstance(); + } + + + @Bean(name = "mongoTemplate") + public MongoTemplate mongoTemplate(MongodProcess mongodProcess) throws Exception { + Net net = mongodProcess.getConfig().net(); + properties.setHost(net.getServerAddress().getHostName()); + properties.setPort(net.getPort()); + properties.setDatabase(originalUri); + properties.setUri(null); + + MongoTemplate template = new MongoTemplate( + new SimpleMongoDbFactory(properties.createMongoClient(this.options, environment), + properties.getDatabase())); + ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); + return template; + } + + @PostConstruct + public void postConstruct() { + //set uri string + originalUri = new ConnectionString(properties.getUri()).getDatabase(); + } + + /** + * Creates a shadow template configuration by adding "-shadow" as postfix of database name. + * This is used to replicate the entire database structure in a shadow/temporary database location + * + * @return + * @throws Exception + */ + @Bean(name = "shadowMongoTemplate") + public MongoTemplate shadowMongoTemplate(MongodProcess mongodProcess) throws Exception { + Net net = mongodProcess.getConfig().net(); + properties.setHost(net.getServerAddress().getHostName()); + properties.setPort(net.getPort()); + properties.setDatabase(originalUri + MongoTemplateConfig.SHADOW_POSTFIX); + properties.setUri(null); + MongoTemplate template = new MongoTemplate( + new SimpleMongoDbFactory(properties.createMongoClient(this.options, environment), + properties.getDatabase())); + ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); + return template; + } +} diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java index 856a1e6c0..9046860e4 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java @@ -1,7 +1,7 @@ package org.devgateway.ocds.persistence.mongo.spring.json; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.After; import org.junit.Assert; @@ -37,7 +37,7 @@ public void importObject() throws Exception { + " budget: {\n" + " description: \"budget desc...\",\n" + " amount: {\n" - + " amount: 10000,\n" + + " amount: 10000.0,\n" + " currency: \"USD\"\n" + " }\n" + " }\n" diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImportTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImportTest.java index e5ea8f6df..3748335a3 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImportTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImportTest.java @@ -1,7 +1,13 @@ package org.devgateway.ocds.persistence.mongo.spring.json; -import org.devgateway.ocds.persistence.mongo.*; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.Amount; +import org.devgateway.ocds.persistence.mongo.Award; +import org.devgateway.ocds.persistence.mongo.Contract; +import org.devgateway.ocds.persistence.mongo.Release; +import org.devgateway.ocds.persistence.mongo.Tag; +import org.devgateway.ocds.persistence.mongo.Tender; +import org.devgateway.ocds.persistence.mongo.Transaction; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.After; import org.junit.Assert; @@ -10,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired; import java.io.File; +import java.net.URI; import java.util.Collection; import java.util.Set; @@ -45,7 +52,7 @@ public void importObjectsPlanning() throws Exception { Assert.assertEquals(1, releases.size()); Assert.assertNotNull(importedRelease); - Assert.assertEquals("GBP", importedRelease.getPlanning().getBudget().getAmount().getCurrency()); + Assert.assertEquals(Amount.Currency.GBP, importedRelease.getPlanning().getBudget().getAmount().getCurrency()); } @Test @@ -75,7 +82,7 @@ public void importObjectsTenderAmendment() throws Exception { Assert.assertEquals(1, releases.size()); Assert.assertNotNull(importedRelease); - Assert.assertArrayEquals(new Tender.SubmissionMethod[] {Tender.SubmissionMethod.electronicSubmission}, + Assert.assertArrayEquals(new String[] {Tender.SubmissionMethod.electronicSubmission.toString()}, importedRelease.getTender().getSubmissionMethod().toArray()); } @@ -126,6 +133,6 @@ public void importObjectsImplementation() throws Exception { Assert.assertArrayEquals(new Tag[] {Tag.implementation}, importedRelease.getTag().toArray()); final Set contracts = importedRelease.getContracts(); final Set transactions = contracts.iterator().next().getImplementation().getTransactions(); - Assert.assertEquals("https://openspending.org/uk-barnet-spending/", transactions.iterator().next().getSource()); + Assert.assertEquals(new URI("https://openspending.org/uk-barnet-spending/"), transactions.iterator().next().getSource()); } } diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json2object/ReleaseJsonToObjectTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json2object/ReleaseJsonToObjectTest.java index ea45e421c..013375534 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json2object/ReleaseJsonToObjectTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json2object/ReleaseJsonToObjectTest.java @@ -1,5 +1,6 @@ package org.devgateway.ocds.persistence.mongo.spring.json2object; +import org.devgateway.ocds.persistence.mongo.Amount; import org.devgateway.ocds.persistence.mongo.Release; import org.junit.Assert; import org.junit.Test; @@ -38,7 +39,7 @@ public void toObject() throws Exception { Assert.assertEquals("IDs are the same", "123", release.getId()); Assert.assertEquals("Check budget amount", new BigDecimal(10000), release.getPlanning().getBudget().getAmount().getAmount()); - Assert.assertEquals("Check budget currency", "USD", + Assert.assertEquals("Check budget currency", Amount.Currency.USD, release.getPlanning().getBudget().getAmount().getCurrency()); } @@ -69,7 +70,7 @@ public void toObjectFromFile() throws Exception { Assert.assertEquals("IDs are the same", "12345", release.getId()); Assert.assertEquals("Check budget amount", new BigDecimal(10000), release.getPlanning().getBudget().getAmount().getAmount()); - Assert.assertEquals("Check budget currency", "RON", + Assert.assertEquals("Check budget currency", Amount.Currency.RON, release.getPlanning().getBudget().getAmount().getCurrency()); } } diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json2object/ReleasePackageJsonToObjectTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json2object/ReleasePackageJsonToObjectTest.java index e08f8284d..bf891bf84 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json2object/ReleasePackageJsonToObjectTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json2object/ReleasePackageJsonToObjectTest.java @@ -1,5 +1,6 @@ package org.devgateway.ocds.persistence.mongo.spring.json2object; +import org.devgateway.ocds.persistence.mongo.Amount; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; import org.junit.Assert; @@ -7,7 +8,6 @@ import java.math.BigDecimal; import java.text.SimpleDateFormat; -import java.util.TimeZone; /** * @author idobre @@ -61,7 +61,7 @@ public void toObject() throws Exception { Assert.assertEquals("Check budget amount", new BigDecimal(10000), release.getPlanning().getBudget().getAmount().getAmount()); - Assert.assertEquals("Check budget currency", "USD", + Assert.assertEquals("Check budget currency", Amount.Currency.USD, release.getPlanning().getBudget().getAmount().getCurrency()); } diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/OrganizationRepositoryTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/OrganizationRepositoryTest.java index b64807c08..e799b6dbb 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/OrganizationRepositoryTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/OrganizationRepositoryTest.java @@ -1,18 +1,20 @@ package org.devgateway.ocds.persistence.mongo.test; -import java.io.IOException; - import org.devgateway.ocds.persistence.mongo.Address; import org.devgateway.ocds.persistence.mongo.ContactPoint; import org.devgateway.ocds.persistence.mongo.Identifier; import org.devgateway.ocds.persistence.mongo.Organization; -import org.devgateway.ocds.persistence.mongo.repository.OrganizationRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.OrganizationRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + public class OrganizationRepositoryTest extends AbstractMongoTest { @Autowired @@ -21,7 +23,7 @@ public class OrganizationRepositoryTest extends AbstractMongoTest { private static final String ORG_ID = "1234"; @Before - public void importTestData() throws IOException, InterruptedException { + public void importTestData() throws IOException, InterruptedException, URISyntaxException { // be sure that the organization collection is empty organizationRepository.deleteAll(); @@ -42,13 +44,13 @@ public void importTestData() throws IOException, InterruptedException { contactPoint.setEmail("mpostelnicu@developmentgateway.org"); contactPoint.setFaxNumber("01234567"); contactPoint.setTelephone("01234567"); - contactPoint.setUrl("http://developmentgateway.org"); + contactPoint.setUrl(new URI("http://developmentgateway.org")); organization.setContactPoint(contactPoint); final Identifier identifier = new Identifier(); organization.getAdditionalIdentifiers().add(identifier); - organization.getRoles().add(Organization.OrganizationType.procuringEntity); - organization.getRoles().add(Organization.OrganizationType.buyer); + organization.getRoles().add(Organization.OrganizationType.procuringEntity.toString()); + organization.getRoles().add(Organization.OrganizationType.buyer.toString()); final Organization savedOrganization = organizationRepository.save(organization); diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/RecordRepositoryTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/RecordRepositoryTest.java index 50cb15fe6..abcf44ffa 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/RecordRepositoryTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/RecordRepositoryTest.java @@ -1,21 +1,20 @@ package org.devgateway.ocds.persistence.mongo.test; -import java.math.BigDecimal; - import org.devgateway.ocds.persistence.mongo.Amount; import org.devgateway.ocds.persistence.mongo.Award; import org.devgateway.ocds.persistence.mongo.Budget; import org.devgateway.ocds.persistence.mongo.Planning; -import org.devgateway.ocds.persistence.mongo.Record; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.RecordRepository; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.RecordRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.ReleaseCompilerService; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.math.BigDecimal; + public class RecordRepositoryTest extends AbstractMongoTest { private String ocid = "release-1"; @@ -53,11 +52,11 @@ private Release getRelease1() { planning.setBudget(budget); final Amount amount = new Amount(); amount.setAmount(BigDecimal.valueOf(1234)); - amount.setCurrency("VND"); + amount.setCurrency(Amount.Currency.VND); budget.setAmount(amount); budget.setDescription("Some description 1"); budget.setProject("A nice project! 1"); - budget.setSource("The source! 1"); + //budget.setSource("The source! 1"); final Award award1 = new Award(); award1.setDescription("Some award1"); @@ -81,11 +80,11 @@ private Release getRelease2() { planning.setBudget(budget); final Amount amount = new Amount(); amount.setAmount(BigDecimal.valueOf(2345)); - amount.setCurrency("USD"); + amount.setCurrency(Amount.Currency.USD); budget.setAmount(amount); budget.setDescription("Some description 2"); budget.setProject("A nice project! 2"); - budget.setSource("The source! 2"); + //budget.setSource("The source! 2"); final Award award1 = new Award(); award1.setDescription("SoMe AwArD 2"); @@ -100,21 +99,22 @@ private Release getRelease2() { return release; } - @Test - public void testRecordSave() { - final Record record = new Record(); - record.setOcid(ocid); - - record.getReleases().add(getRelease1()); - record.getReleases().add(getRelease2()); - - recordRepository.save(record); - - releaseCompilerService.createSaveCompiledReleaseAndSaveRecord(record); - - Assert.assertNotNull(record.getCompiledRelease()); - - Assert.assertEquals(3, record.getCompiledRelease().getAwards().size()); - } + //TODO: we need to migrate merging to the new format in OCDS 1.1.x +// @Test +// public void testRecordSave() { +// final Record record = new Record(); +// record.setOcid(ocid); +// +// record.getReleases().add(getRelease1()); +// record.getReleases().add(getRelease2()); +// +// recordRepository.save(record); +// +// releaseCompilerService.createSaveCompiledReleaseAndSaveRecord(record); +// +// Assert.assertNotNull(record.getCompiledRelease()); +// +// Assert.assertEquals(3, record.getCompiledRelease().getAwards().size()); +// } } diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/ReleaseRepositoryTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/ReleaseRepositoryTest.java index 4f690dc78..7cca0e72f 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/ReleaseRepositoryTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/ReleaseRepositoryTest.java @@ -1,7 +1,7 @@ package org.devgateway.ocds.persistence.mongo.test; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.Assert; import org.junit.Test; diff --git a/persistence-mongodb/src/test/java/org/devgateway/toolkit/persistence/mongo/AbstractMongoTest.java b/persistence-mongodb/src/test/java/org/devgateway/toolkit/persistence/mongo/AbstractMongoTest.java index 6b4d15d9f..617064bdb 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/toolkit/persistence/mongo/AbstractMongoTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/toolkit/persistence/mongo/AbstractMongoTest.java @@ -10,7 +10,7 @@ @RunWith(SpringRunner.class) @ActiveProfiles("integration") -@SpringBootTest(classes = { MongoPersistenceApplication.class }) +@SpringBootTest(classes = { MongoPersistenceApplication.class }, properties = {"net.sf.ehcache.disabled=true"}) @TestPropertySource("classpath:test.properties") @AutoConfigureCache /** diff --git a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-01-planning.json b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-01-planning.json index d5f4910ea..59166de92 100755 --- a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-01-planning.json +++ b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-01-planning.json @@ -1,187 +1,199 @@ { - "uri":"http://standard.open-contracting.org/examples/releases/ocds-213czf-000-00001-01-planning.json", - "publishedDate":"2009-03-15T14:45:00Z", + "version": "1.1", + "uri": "https://github.com/open-contracting/sample-data/raw/1.1/fictional-example/ocds-213czf-000-00001-01-planning.json", + "publishedDate": "2009-03-15T14:45:00Z", "publisher": { - "scheme": "GB-COH", - "uid": "09506232", "name": "Open Data Services Co-operative Limited", - "uri": "http://standard.open-contracting.org/examples/" + "scheme": "GB-COH", + "uid": "9506232.0", + "uri": "http://data.companieshouse.gov.uk/doc/company/09506232" }, - "license":"http://opendatacommons.org/licenses/pddl/1.0/", - "publicationPolicy":"https://github.com/open-contracting/sample-data/", - "releases":[{ - "language": "en", - "ocid": "ocds-213czf-000-00001", - "id": "ocds-213czf-000-00001-01-planning", - "date": "2009-03-15T14:45:00Z", - "tag": ["planning"], - "initiationType": "tender", - "buyer": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" - }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" - }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "planning": { - "budget": { - "source": "Highways and Pavements Budget", - "id": "6801ad388f3a38b7740dde20108c58b35984ee91", - "description": "Budget allocation for highway maintenance, aligned with 2015 strategic plan. ", - "amount": { - "amount": 6700000.00, - "currency": "GBP" - }, - "project": "Central Junction Cycle Scheme", - "projectID": "SP001", - "uri": "https://openspending.org/uk-barnet-budget/entries/6801ad388f3a38b7740dde20108c58b35984ee91" - }, - "rationale": "The 2009 Strategic Plan identifies a need for an improved cycle route in the centre of town.", - "documents": [ - { - "id": "0001", - "documentType": "procurementPlan", - "title": "Area Wide Cycle Improvements - Procurement Plan", - "description": "The overall strategic framework for procurement to enhance cycle provision.", - "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", - "datePublished": "2009-01-05T00:00:00Z", - "format": "application/pdf", - "language": "en" - }, - { - "id": "0002", - "documentType": "needsAssessment", - "title": "Cycle provision - Needs Assessment", - "description": "Needs assessment for provision for cyclists in the centre of town.", - "url": "http://example.com/opencontracting/documents/ocds-213czf-000-00001/needsAssessment.pdf", - "datePublished": "2009-01-15T00:00:00Z", - "format": "application/pdf", - "language": "en" - } - ] - }, - "tender": { + "license": "http://opendatacommons.org/licenses/pddl/1.0/", + "publicationPolicy": "https://github.com/open-contracting/sample-data/", + "releases": [ + { + "ocid": "ocds-213czf-000-00001", "id": "ocds-213czf-000-00001-01-planning", - "title": "Planned cycle lane improvements", - "description": "The authority plans to tender for improvements to the cycle lane in early 2010. This notice provides advanced notice of the intention to tender, and details to upcoming consultation events.", - "status": "planned", - "items": [ + "language": "en", + "date": "2009-03-15T14:45:00Z", + "initiationType": "tender", + "tag": [ + "planning" + ], + "parties": [ { - "id": "0001", - "description": "string", - "classification": { - "scheme": "CPV", - "id": "45233130", - "description": "Construction work for highways", - "uri": "http://cpv.data.ac.uk/code-45233130" - }, - "additionalClassifications": [ - { - "scheme": "CPV", - "id": "45233162-2", - "description": "Cycle path construction work", - "uri": "http://cpv.data.ac.uk/code-45233162.html" - } + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet", + "roles": [ + "buyer" ], - "quantity": 10, - "unit": { - "name": "Miles", - "value": { - "amount": 100000, - "currency": "GBP" - } + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet" + }, + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" } } ], - "minValue": { - "amount": 500000, - "currency": "GBP" + "buyer": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" }, - "value": { - "amount": 1000000, - "currency": "GBP" - }, - "procurementMethod": "open", - "procurementMethodRationale": "An open competitive tender is required by EU Rules", - "awardCriteria": "bestProposal", - "awardCriteriaDetails": "The best proposal, subject to value for money requirements, will be accepted.", - "tenderPeriod": { - "startDate": "2010-02-01T00:00:00Z" - }, - "awardPeriod": { - "startDate": "2010-06-01T00:00:00Z", - "endDate": "2011-06-01T23:59:59Z" + "planning": { + "budget": { + "id": "6801ad388f3a38b7740dde20108c58b35984ee91", + "description": "Budget allocation for highway maintenance, aligned with 2015 strategic plan. ", + "amount": { + "amount": 6700000, + "currency": "GBP" + }, + "project": "Central Junction Cycle Scheme", + "projectID": "SP001", + "uri": "https://openspending.org/uk-barnet-budget/entries/6801ad388f3a38b7740dde20108c58b35984ee91" + }, + "rationale": "The 2009 Strategic Plan identifies a need for an improved cycle route in the centre of town.", + "documents": [ + { + "id": "1.0", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + }, + { + "id": "2.0", + "documentType": "needsAssessment", + "title": "Cycle provision - Needs Assessment", + "description": "Needs assessment for provision for cyclists in the centre of town.", + "url": "http://example.com/opencontracting/documents/ocds-213czf-000-00001/needsAssessment.pdf", + "datePublished": "2009-01-15T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ], + "milestones": [ + { + "id": "1.0", + "type": "preProcurement", + "title": "Strategic outline procurement plan approval", + "description": "Approval of the strategic outline procurement plan by the procurement steering group", + "status": "met", + "dueDate": "2009-03-14T17:00:00Z", + "dateMet": "2009-03-14T17:00:00Z" + }, + { + "id": "2.0", + "type": "engagement", + "title": "Public consultation", + "description": "A public consultation on the proposed cycle lane will be held at the council offices", + "status": "scheduled", + "dueDate": "2009-05-01T09:00:00Z" + } + ] }, - "procuringEntity": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" + "tender": { + "id": "ocds-213czf-000-00001-01-planning", + "title": "Planned cycle lane improvements", + "description": "The authority plans to tender for improvements to the cycle lane in early 2010. This notice provides advanced notice of the intention to tender, and details to upcoming consultation events.", + "mainProcurementCategory": "works", + "status": "planned", + "minValue": { + "amount": 500000, + "currency": "GBP" }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" + "value": { + "amount": 1000000, + "currency": "GBP" }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "documents": [ - { - "id": "0003", - "documentType": "x_consultationDocument", - "title": "Consultation on cycle provision", - "description": "A consultation document inviting citizen input into cycle provision.", - "url": "http://example.com/consultations/cycle-provision/", - "datePublished": "2015-02-15T00:00:00Z", - "dateModified": "2015-02-15T00:00:00Z", - "format": "text/html", - "language": "en" + "procurementMethod": "open", + "procurementMethodDetails": "In open procedures, any interested economic operator may submit a tender in response to a contract notice. ", + "procurementMethodRationale": "An open competitive tender is required by EU Rules", + "awardCriteria": "bestProposal", + "awardCriteriaDetails": "The best proposal, subject to value for money requirements, will be accepted.", + "tenderPeriod": { + "startDate": "2010-02-01T00:00:00Z", + "durationInDays": 31 }, - { - "id": "0004", - "documentType": "x_map", - "title": "Map of affected areas", - "description": "A map showing areas affected by the planned highway updates. Available from local libraries.", - "datePublished": "2015-02-15T00:00:00Z", - "format": "offline/print", - "language": "en" - } - ], - "milestones": [ - { - "id": "0001", - "title": "Consultation Period", - "description": "A consultation period is open for citizen input to shape the final plans.", - "dueDate": "2015-04-15T17:00:00Z" - } - ] + "enquiryPeriod": { + "durationInDays": 14 + }, + "contractPeriod": { + "startDate": "2010-06-01T00:00:00Z", + "endDate": "2011-05-31T23:59:00Z", + "durationInDays": 365 + }, + "procuringEntity": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" + }, + "documents": [ + { + "id": "3.0", + "documentType": "x_consultationDocument", + "title": "Consultation on cycle provision", + "description": "A consultation document inviting citizen input into cycle provision.", + "url": "http://example.com/consultations/cycle-provision/", + "datePublished": "2010-02-15T00:00:00Z", + "format": "text/html", + "language": "en" + }, + { + "id": "4.0", + "documentType": "x_map", + "title": "Map of affected areas", + "description": "A map showing areas affected by the planned highway updates. Available from local libraries.", + "datePublished": "2010-02-15T00:00:00Z", + "format": "offline/print", + "language": "en" + } + ], + "items": [ + { + "id": "1.0", + "description": "Cycle lane improvements", + "classification": { + "scheme": "CPV", + "id": "45233130.0", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "quantity": 10, + "unit": { + "name": "Miles", + "id": "SMI", + "scheme": "UNCEFACT", + "value": { + "amount": 100000, + "currency": "GBP" + } + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ] + } + ] + } } - - }] -} + ] +} \ No newline at end of file diff --git a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-02-tender.json b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-02-tender.json index 32b945b7a..55b43bbae 100755 --- a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-02-tender.json +++ b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-02-tender.json @@ -1,140 +1,147 @@ { - "uri":"http://standard.open-contracting.org/examples/releases/ocds-213czf-000-00001-02-tender.json", - "publishedDate":"2010-03-01T09:30:00Z", + "version": "1.1", + "uri": "https://github.com/open-contracting/sample-data/raw/1.1/fictional-example/ocds-213czf-000-00001-02-tender.json", + "publishedDate": "2010-03-15T09:30:00Z", "publisher": { - "scheme": "GB-COH", - "uid": "09506232", "name": "Open Data Services Co-operative Limited", - "uri": "http://standard.open-contracting.org/examples/" + "scheme": "GB-COH", + "uid": "9506232.0", + "uri": "http://data.companieshouse.gov.uk/doc/company/09506232" }, - "license":"http://opendatacommons.org/licenses/pddl/1.0/", - "publicationPolicy":"https://github.com/open-contracting/sample-data/", - "releases":[{ - "language": "en", - "ocid": "ocds-213czf-000-00001", - "id": "ocds-213czf-000-00001-02-tender", - "date": "2010-03-15T09:30:00Z", - "tag": ["tender"], - "initiationType": "tender", - "buyer": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" - }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" - }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "tender": { - "id": "ocds-213czf-000-00001-01-tender", - "title": "Planned cycle lane improvements", - "description": "Tenders solicited for work to build new cycle lanes in the centre of town.", - "status": "active", - "items": [ + "license": "http://opendatacommons.org/licenses/pddl/1.0/", + "publicationPolicy": "https://github.com/open-contracting/sample-data/", + "releases": [ + { + "ocid": "ocds-213czf-000-00001", + "id": "ocds-213czf-000-00001-02-tender", + "language": "en", + "date": "2010-03-15T09:30:00Z", + "initiationType": "tender", + "tag": [ + "tender" + ], + "parties": [ { - "id": "0001", - "description": "string", - "classification": { - "scheme": "CPV", - "id": "45233130", - "description": "Construction work for highways", - "uri": "http://cpv.data.ac.uk/code-45233130" - }, - "additionalClassifications": [ - { - "scheme": "CPV", - "id": "45233162-2", - "description": "Cycle path construction work", - "uri": "http://cpv.data.ac.uk/code-45233162.html" - } + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet", + "roles": [ + "buyer" ], - "quantity": 8, - "unit": { - "name": "Miles", - "value": { - "amount": 120000, - "currency": "GBP" - } + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet" + }, + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" } } ], - "minValue": { - "amount": 600000, - "currency": "GBP" - }, - "value": { - "amount": 1100000, - "currency": "GBP" - }, - "procurementMethod": "open", - "procurementMethodRationale": "An open competitive tender is required by EU Rules", - "awardCriteria": "bestProposal", - "awardCriteriaDetails": "The best proposal, subject to value for money requirements, will be accepted.", - "submissionMethod": [ "electronicSubmission" ], - "submissionMethodDetails": "Submit through the online portal at http://example.com/submissions/ocds-213czf-000-00001-01/", - "enquiryPeriod": { - "startDate": "2010-03-01T09:00:00Z", - "endDate": "2010-03-14T17:30:00Z" - }, - "hasEnquiries": false, - "tenderPeriod": { - "startDate": "2010-03-01T09:00:00Z", - "endDate": "2011-04-01T18:00:00Z" - - }, - "awardPeriod": { - "startDate": "2010-06-01T00:00:00Z", - "endDate": "2011-08-01T23:59:59Z" + "buyer": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" }, - "procuringEntity": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" + "tender": { + "id": "ocds-213czf-000-00001-01-tender", + "title": "Planned cycle lane improvements", + "description": "Tenders solicited for work to build new cycle lanes in the centre of town.", + "mainProcurementCategory": "works", + "status": "active", + "minValue": { + "amount": 600000, + "currency": "GBP" }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" + "value": { + "amount": 1100000, + "currency": "GBP" }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "documents": [ - { - "id": "0005", - "documentType": "notice", - "title": "Tender Notice", - "description": "Official tender notice.", - "url": "http://example.com/tender-notices/ocds-213czf-000-00001-01.html", - "datePublished": "2010-03-01T09:00:00Z", - "format": "text/html", - "language": "en" - } - ] + "procurementMethod": "open", + "procurementMethodDetails": "In open procedures, any interested economic operator may submit a tender in response to a contract notice. ", + "procurementMethodRationale": "An open competitive tender is required by EU Rules", + "awardCriteria": "bestProposal", + "awardCriteriaDetails": "The best proposal, subject to value for money requirements, will be accepted.", + "submissionMethod": [ + "electronicSubmission" + ], + "submissionMethodDetails": "Submit through the online portal at http://example.com/submissions/ocds-213czf-000-00001-01/", + "tenderPeriod": { + "startDate": "2010-03-01T09:00:00Z", + "endDate": "2010-04-01T18:00:00Z", + "durationInDays": 31 + }, + "enquiryPeriod": { + "startDate": "2010-03-01T09:00:00Z", + "endDate": "2010-03-14T17:30:00Z", + "durationInDays": 14 + }, + "awardPeriod": { + "startDate": "2010-04-02T09:00:00Z", + "endDate": "2010-05-01T18:00:00Z" + }, + "contractPeriod": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-06-30T23:59:00Z", + "durationInDays": 365 + }, + "procuringEntity": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" + }, + "hasEnquiries": false, + "documents": [ + { + "id": "5.0", + "documentType": "tenderNotice", + "title": "Tender Notice", + "description": "Official tender notice.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-01.html", + "datePublished": "2010-03-01T09:00:00Z", + "format": "text/html", + "language": "en" + } + ], + "items": [ + { + "id": "1.0", + "description": "Cycle lane improvements", + "classification": { + "scheme": "CPV", + "id": "45233130.0", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "quantity": 8, + "unit": { + "name": "Miles", + "id": "SMI", + "scheme": "UNCEFACT", + "value": { + "amount": 120000, + "currency": "GBP" + } + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ] + } + ] + } } - }] -} + ] +} \ No newline at end of file diff --git a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-03-tenderAmendment.json b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-03-tenderAmendment.json index 1137a4550..7f27dd02d 100755 --- a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-03-tenderAmendment.json +++ b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-03-tenderAmendment.json @@ -1,159 +1,167 @@ { - "uri":"http://standard.open-contracting.org/examples/releases/ocds-213czf-000-00001-03-tenderAmendment.json", - "publishedDate":"2010-03-20T09:45:00Z", + "version": "1.1", + "uri": "https://github.com/open-contracting/sample-data/raw/1.1/fictional-example/ocds-213czf-000-00001-03-tenderAmendment.json", + "publishedDate": "2010-03-20T09:45:00Z", "publisher": { - "scheme": "GB-COH", - "uid": "09506232", "name": "Open Data Services Co-operative Limited", - "uri": "http://standard.open-contracting.org/examples/" + "scheme": "GB-COH", + "uid": "9506232.0", + "uri": "http://data.companieshouse.gov.uk/doc/company/09506232" }, - "license":"http://opendatacommons.org/licenses/pddl/1.0/", - "publicationPolicy":"https://github.com/open-contracting/sample-data/", - "releases":[{ - "language": "en", - "ocid": "ocds-213czf-000-00001", - "id": "ocds-213czf-000-00001-03-tenderAmendment", - "date": "2010-03-20T09:45:00Z", - "tag": ["tenderAmendment"], - "initiationType": "tender", - "buyer": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" - }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" - }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "tender": { - "id": "ocds-213czf-000-00001-01-tender", - "title": "Planned cycle lane improvements", - "description": "Tenders solicited for work to build new cycle lanes in the centre of town.", - "status": "active", - "items": [ + "license": "http://opendatacommons.org/licenses/pddl/1.0/", + "publicationPolicy": "https://github.com/open-contracting/sample-data/", + "releases": [ + { + "ocid": "ocds-213czf-000-00001", + "id": "ocds-213czf-000-00001-03-tenderAmendment", + "language": "en", + "date": "2010-03-20T09:45:00Z", + "initiationType": "tender", + "tag": [ + "tenderAmendment" + ], + "parties": [ { - "id": "0001", - "description": "string", - "classification": { - "scheme": "CPV", - "id": "45233130", - "description": "Construction work for highways", - "uri": "http://cpv.data.ac.uk/code-45233130" - }, - "additionalClassifications": [ - { - "scheme": "CPV", - "id": "45233162-2", - "description": "Cycle path construction work", - "uri": "http://cpv.data.ac.uk/code-45233162.html" - } + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet", + "roles": [ + "buyer" ], - "quantity": 8, - "unit": { - "name": "Miles", - "value": { - "amount": 120000, - "currency": "GBP" - } + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet" + }, + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" } } ], - "minValue": { - "amount": 600000, - "currency": "GBP" - }, - "value": { - "amount": 1100000, - "currency": "GBP" - }, - "procurementMethod": "open", - "procurementMethodRationale": "An open competitive tender is required by EU Rules", - "awardCriteria": "bestProposal", - "awardCriteriaDetails": "The best proposal, subject to value for money requirements, will be accepted.", - "submissionMethod": [ "electronicSubmission" ], - "submissionMethodDetails": "Submit through the online portal at http://example.com/submissions/ocds-213czf-000-00001-01/", - "enquiryPeriod": { - "startDate": "2010-03-01T09:00:00Z", - "endDate": "2010-03-14T17:30:00Z" + "buyer": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" }, - "hasEnquiries": true, - "tenderPeriod": { - "startDate": "2010-03-01T09:00:00Z", - "endDate": "2011-04-01T18:00:00Z" - - }, - "awardPeriod": { - "startDate": "2010-06-01T00:00:00Z", - "endDate": "2011-08-01T23:59:59Z" - }, - "procuringEntity": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" + "tender": { + "id": "ocds-213czf-000-00001-01-tender", + "title": "Planned cycle lane improvements", + "description": "Tenders solicited for work to build new cycle lanes in the centre of town.", + "mainProcurementCategory": "works", + "status": "active", + "minValue": { + "amount": 600000, + "currency": "GBP" }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" + "value": { + "amount": 1100000, + "currency": "GBP" }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "documents": [ - { - "id": "0005", - "documentType": "notice", - "title": "Tender Notice", - "description": "Official tender notice.", - "url": "http://example.com/tender-notices/ocds-213czf-000-00001-01.html", - "datePublished": "2010-03-01T09:00:00Z", - "format": "text/html", - "language": "en" + "procurementMethod": "open", + "procurementMethodDetails": "In open procedures, any interested economic operator may submit a tender in response to a contract notice. ", + "procurementMethodRationale": "An open competitive tender is required by EU Rules", + "awardCriteria": "bestProposal", + "awardCriteriaDetails": "The best proposal, subject to value for money requirements, will be accepted.", + "submissionMethod": [ + "electronicSubmission" + ], + "submissionMethodDetails": "Submit through the online portal at http://example.com/submissions/ocds-213czf-000-00001-01/", + "tenderPeriod": { + "startDate": "2010-03-01T09:00:00Z", + "endDate": "2010-04-08T18:00:00Z", + "durationInDays": 31 }, - { - "id": "0006", - "documentType": "enquiryResponses", - "title": "Enquiry Responses", - "description": "Responses to enquiries asked by interested parties.", - "url": "http://example.com/enquiry-response/ocds-213czf-000-00001-01.html", - "datePublished": "2010-03-20T11:30:15Z", - "format": "text/html", - "language": "en" - } - ], - "amendment": { - "date": "2010-03-20T09:45:00Z", - "changes": [ - {"property": "documents"}, - {"property": "hasEnquiries"} + "enquiryPeriod": { + "startDate": "2010-03-01T09:00:00Z", + "endDate": "2010-03-14T17:30:00Z", + "durationInDays": 14 + }, + "awardPeriod": { + "startDate": "2010-04-02T09:00:00Z", + "endDate": "2010-05-01T18:00:00Z" + }, + "contractPeriod": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-06-30T23:59:00Z", + "durationInDays": 365 + }, + "procuringEntity": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" + }, + "hasEnquiries": true, + "documents": [ + { + "id": "5.0", + "documentType": "tenderNotice", + "title": "Tender Notice", + "description": "Official tender notice.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-01.html", + "datePublished": "2010-03-01T09:00:00Z", + "format": "text/html", + "language": "en" + }, + { + "id": "6.0", + "documentType": "clarifications", + "title": "Enquiry Responses", + "description": "Responses to enquiries asked by interested parties.", + "url": "http://example.com/enquiry-response/ocds-213czf-000-00001-01.html", + "datePublished": "2010-03-20T11:30:15Z", + "format": "text/html", + "language": "en" + } ], - "rationale": "Following the enquiry period, enquiries were received and responses to questions asked have been published. No changes to the overall tender details were made." + "items": [ + { + "id": "1.0", + "description": "Cycle lane improvements", + "classification": { + "scheme": "CPV", + "id": "45233130.0", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "quantity": 8, + "unit": { + "name": "Miles", + "id": "SMI", + "scheme": "UNCEFACT", + "value": { + "amount": 120000, + "currency": "GBP" + } + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ] + } + ], + "amendments": [ + { + "date": "2010-03-20T09:45:00Z", + "rationale": "The tender period has been extended in accordance with council guidelines", + "id": "1.0", + "description": "The closing date for tender submissions has been extended to 2017-04-08", + "amendsReleaseID": "ocds-213czf-000-00001-02-tender", + "releaseID": "ocds-213czf-000-00001-03-tenderAmendment" + } + ] } } - - }] -} + ] +} \ No newline at end of file diff --git a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-04-award.json b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-04-award.json index 4cc4b0ad9..8c27da372 100755 --- a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-04-award.json +++ b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-04-award.json @@ -1,131 +1,194 @@ { - "uri":"http://standard.open-contracting.org/examples/releases/ocds-213czf-000-00001-04-award.json", - "publishedDate":"2010-05-10T09:30:00Z", + "version": "1.1", + "uri": "https://github.com/open-contracting/sample-data/raw/1.1/fictional-example/ocds-213czf-000-00001-04-award.json", + "publishedDate": "2010-05-10T09:30:00Z", "publisher": { - "scheme": "GB-COH", - "uid": "09506232", "name": "Open Data Services Co-operative Limited", - "uri": "http://standard.open-contracting.org/examples/" + "scheme": "GB-COH", + "uid": "9506232.0", + "uri": "http://data.companieshouse.gov.uk/doc/company/09506232" }, - "license":"http://opendatacommons.org/licenses/pddl/1.0/", - "publicationPolicy":"https://github.com/open-contracting/sample-data/", - "releases":[{ - "language": "en", - "ocid": "ocds-213czf-000-00001", - "id": "ocds-213czf-000-00001-04-award", - "date": "2010-05-10T09:30:00Z", - "tag": ["award"], - "initiationType": "tender", - "buyer": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" - }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" - }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "awards": [ - { - "id": "ocds-213czf-000-00001-award-01", - "title": "Award of contract to build new cycle lanes in the centre of town.", - "description": "AnyCorp Ltd has been awarded the contract to build new cycle lanes in the centre of town.", - "status": "pending", - "date": "2010-05-10T09:30:00Z", - "value": { - "amount": 11000000, - "currency": "GBP" - }, - "suppliers": [ - { - "identifier": { - "scheme": "GB-COH", - "id": "1234567844", - "legalName": "AnyCorp Ltd", - "uri": "http://www.anycorp.example" - }, - "additionalIdentifiers": [ - { - "scheme": "GB-VAT", - "id": "987654321" - } - ], - "name": "AnyCorp Cycle Provision", - "address": { - "streetAddress": "100 Anytown Lane", - "locality": "Anytown", - "region": "AnyCounty", - "postalCode": "AN1 1TN", - "countryName": "United Kingdom" - }, - "contactPoint": { - "name": "Contracts Team", - "email": "contracts@anycorp.example", - "telephone": "12345 456 343", - "faxNumber": "12345 456 343" - } + "license": "http://opendatacommons.org/licenses/pddl/1.0/", + "publicationPolicy": "https://github.com/open-contracting/sample-data/", + "releases": [ + { + "ocid": "ocds-213czf-000-00001", + "id": "ocds-213czf-000-00001-04-award", + "language": "en", + "date": "2010-05-10T09:30:00Z", + "initiationType": "tender", + "tag": [ + "award" + ], + "parties": [ + { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet", + "roles": [ + "buyer" + ], + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet" + }, + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" } - ], - "items": [ - { - "id": "0001", - "description": "string", - "classification": { - "scheme": "CPV", - "id": "45233130", - "description": "Construction work for highways", - "uri": "http://cpv.data.ac.uk/code-45233130" - }, - "additionalClassifications": [ - { - "scheme": "CPV", - "id": "45233162-2", - "description": "Cycle path construction work", - "uri": "http://cpv.data.ac.uk/code-45233162.html" - } - ], - "quantity": 8, - "unit": { - "name": "Miles", - "value": { - "amount": 137000, - "currency": "GBP" - } - } + }, + { + "id": "GB-COH-11111111", + "name": "AnyCorp Cycle Provision", + "roles": [ + "supplier" + ], + "identifier": { + "scheme": "GB-COH", + "id": "11111111.0", + "legalName": "AnyCorp Ltd" + }, + "address": { + "streetAddress": "1 Anytown Lane", + "locality": "Anytown", + "region": "Any County", + "postalCode": "AA1 1AA", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Contracts Team", + "email": "contracts@anycorp.example", + "telephone": "11111 111 111", + "faxNumber": "11111 111 112", + "url": "http://www.anycorp.example" } - ], - "contractPeriod": { - "startDate": "2010-07-01T00:00:00Z", - "endDate": "2011-08-01T23:59:00Z" }, - "documents": [ + { + "id": "GB-COH-22222222", + "name": "Beta Corp Construction", + "roles": [ + "tenderer" + ], + "identifier": { + "scheme": "GB-COH", + "id": "22222222.0", + "legalName": "Beta Corp Ltd" + }, + "address": { + "streetAddress": "2 Main Road", + "locality": "Beta City", + "region": "Beta County", + "postalCode": "BB2 2BB", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Bids Team", + "email": "bids@betacorp.example", + "telephone": "22222 222 222", + "faxNumber": "22222 222 223", + "url": "http://www.betacorp.example" + } + } + ], + "buyer": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" + }, + "tender": { + "id": "ocds-213czf-000-00001-01-tender", + "title": "Planned cycle lane improvements", + "description": "Tenders solicited for work to build new cycle lanes in the centre of town.", + "mainProcurementCategory": "works", + "status": "complete", + "numberOfTenderers": 2, + "hasEnquiries": true, + "tenderers": [ { - "id": "0007", - "documentType": "notice", - "title": "Award notice", - "description": "Award of contract to build new cycle lanes in the centre of town to AnyCorp Ltd.", - "url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html", - "datePublished": "2010-05-10T09:30:00Z", - "format": "text/html", - "language": "en" + "id": "GB-COH-11111111", + "name": "AnyCorp Cycle Provision" + }, + { + "id": "GB-COH-22222222", + "name": "Beta Corp Construction" } ] - } - ] - - }] -} + }, + "awards": [ + { + "id": "ocds-213czf-000-00001-award-01", + "title": "Award of contract to build new cycle lanes in the centre of town.", + "description": "AnyCorp Ltd has been awarded the contract to build new cycle lanes in the centre of town.", + "status": "pending", + "date": "2010-05-10T09:30:00Z", + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "suppliers": [ + { + "id": "GB-COH-11111111", + "name": "AnyCorp Cycle Provision" + } + ], + "contractPeriod": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-06-30T23:59:00Z", + "maxExtentDate": "2011-12-31T23:59:00Z" + }, + "items": [ + { + "id": "1.0", + "description": "Cycle lane improvements", + "classification": { + "scheme": "CPV", + "id": "45233130.0", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "quantity": 8, + "unit": { + "name": "Miles", + "id": "SMI", + "scheme": "UNCEFACT", + "value": { + "amount": 137000, + "currency": "GBP" + } + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ] + } + ], + "documents": [ + { + "id": "7.0", + "documentType": "awardNotice", + "title": "Award notice", + "description": "Award of contract to build new cycle lanes in the centre of town to AnyCorp Ltd.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html", + "datePublished": "2010-05-10T09:30:00Z", + "format": "text/html", + "language": "en" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-05-contract.json b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-05-contract.json index 1213b10e1..600a853b0 100755 --- a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-05-contract.json +++ b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-05-contract.json @@ -1,191 +1,184 @@ { - "uri":"http://standard.open-contracting.org/examples/releases/ocds-213czf-000-00001-05-contract.json", - "publishedDate":"2010-06-10T10:30:00Z", + "version": "1.1", + "uri": "https://github.com/open-contracting/sample-data/raw/1.1/fictional-example/ocds-213czf-000-00001-05-contract.json", + "publishedDate": "2010-05-10T10:30:00Z", "publisher": { - "scheme": "GB-COH", - "uid": "09506232", "name": "Open Data Services Co-operative Limited", - "uri": "http://standard.open-contracting.org/examples/" + "scheme": "GB-COH", + "uid": "9506232.0", + "uri": "http://data.companieshouse.gov.uk/doc/company/09506232" }, - "license":"http://opendatacommons.org/licenses/pddl/1.0/", - "publicationPolicy":"https://github.com/open-contracting/sample-data/", - "releases":[{ - "language": "en", - "ocid": "ocds-213czf-000-00001", - "id": "ocds-213czf-000-00001-05-contract", - "date": "2010-05-10T10:30:00Z", - "tag": ["contract"], - "initiationType": "tender", - "buyer": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" - }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" + "license": "http://opendatacommons.org/licenses/pddl/1.0/", + "publicationPolicy": "https://github.com/open-contracting/sample-data/", + "releases": [ + { + "ocid": "ocds-213czf-000-00001", + "id": "ocds-213czf-000-00001-05-contract", + "language": "en", + "date": "2010-05-10T10:30:00Z", + "initiationType": "tender", + "tag": [ + "contract" + ], + "parties": [ + { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet", + "roles": [ + "buyer" + ], + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet" + }, + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" + } + } + ], + "buyer": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "awards": [ - { - "id": "ocds-213czf-000-00001-award-01", - "title": "Award of contract to build new cycle lanes in the centre of town.", - "description": "AnyCorp Ltd has been awarded the contract to build new cycle lanes in the centre of town.", - "status": "active", - "date": "2010-05-10T10:30:00Z", - "value": { - "amount": 11000000, - "currency": "GBP" - }, - "suppliers": [ - { - "identifier": { - "scheme": "GB-COH", - "id": "1234567844", - "legalName": "AnyCorp Ltd", - "uri": "http://www.anycorp.example" - }, - "additionalIdentifiers": [ - { - "scheme": "GB-VAT", - "id": "987654321" - } - ], - "name": "AnyCorp Cycle Provision", - "address": { - "streetAddress": "100 Anytown Lane", - "locality": "Anytown", - "region": "AnyCounty", - "postalCode": "AN1 1TN", - "countryName": "United Kingdom" - }, - "contactPoint": { - "name": "Contracts Team", - "email": "contracts@anycorp.example", - "telephone": "12345 456 343", - "faxNumber": "12345 456 343" + "awards": [ + { + "id": "ocds-213czf-000-00001-award-01", + "title": "Award of contract to build new cycle lanes in the centre of town.", + "description": "AnyCorp Ltd has been awarded the contract to build new cycle lanes in the centre of town.", + "status": "active", + "date": "2010-05-10T10:30:00Z", + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "suppliers": [ + { + "id": "GB-COH-11111111", + "name": "AnyCorp Cycle Provision" } - } - ], - "items": [ - { - "id": "0001", - "description": "string", - "classification": { - "scheme": "CPV", - "id": "45233130", - "description": "Construction work for highways", - "uri": "http://cpv.data.ac.uk/code-45233130" - }, - "additionalClassifications": [ - { + ], + "contractPeriod": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-06-30T23:59:00Z", + "maxExtentDate": "2011-12-31T23:59:00Z" + }, + "items": [ + { + "id": "1.0", + "description": "Cycle lane improvements", + "classification": { "scheme": "CPV", - "id": "45233162-2", - "description": "Cycle path construction work", - "uri": "http://cpv.data.ac.uk/code-45233162.html" - } - ], - "quantity": 8, - "unit": { - "name": "Miles", - "value": { - "amount": 137000, - "currency": "GBP" - } + "id": "45233130.0", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "quantity": 8, + "unit": { + "name": "Miles", + "id": "SMI", + "scheme": "UNCEFACT", + "value": { + "amount": 137000, + "currency": "GBP" + } + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ] } - } - ], - "contractPeriod": { - "startDate": "2010-07-01T00:00:00Z", - "endDate": "2011-08-01T23:59:00Z" - }, - "documents": [ - { - "id": "0007", - "documentType": "notice", - "title": "Award notice", - "description": "Award of contract to build new cycle lanes in the centre of town to AnyCorp Ltd.", - "url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html", - "datePublished": "2010-05-10T10:30:00Z", - "format": "text/html", - "language": "en" - } - ] - } - ], - "contracts": [ - { - "id": "ocds-213czf-000-00001-contract-01", - "awardID": "ocds-213czf-000-00001-award-01", - "title": "Contract to build new cycle lanes in the centre of town.", - "description": "A contract has been signed between the Council and AnyCorp Ltd for construction of new cycle lanes in the centre of town.", - "status": "active", - "period": { - "startDate": "2010-07-01T00:00:00Z", - "endDate": "2011-08-01T23:59:00Z" - }, - "value": { - "amount": 11000000, - "currency": "GBP" - }, - "items": [ - { - "id": "0001", - "description": "string", - "classification": { - "scheme": "CPV", - "id": "45233130", - "description": "Construction work for highways", - "uri": "http://cpv.data.ac.uk/code-45233130" - }, - "additionalClassifications": [ - { + ], + "documents": [ + { + "id": "7.0", + "documentType": "awardNotice", + "title": "Award notice", + "description": "Award of contract to build new cycle lanes in the centre of town to AnyCorp Ltd.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html", + "datePublished": "2010-05-10T10:30:00Z", + "format": "text/html", + "language": "en" + } + ] + } + ], + "contracts": [ + { + "id": "ocds-213czf-000-00001-contract-01", + "awardID": "ocds-213czf-000-00001-award-01", + "title": "Contract to build new cycle lanes in the centre of town.", + "description": "A contract has been signed between the Council and AnyCorp Ltd for construction of new cycle lanes in the centre of town.", + "status": "active", + "period": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-06-30T23:59:00Z", + "maxExtentDate": "2011-12-31T23:59:00Z" + }, + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "dateSigned": "2010-06-10T14:23:12Z", + "items": [ + { + "id": "1.0", + "description": "Cycle lane improvements", + "classification": { "scheme": "CPV", - "id": "45233162-2", - "description": "Cycle path construction work", - "uri": "http://cpv.data.ac.uk/code-45233162.html" - } - ], - "quantity": 8, - "unit": { - "name": "Miles", - "value": { - "amount": 137000, - "currency": "GBP" - } + "id": "45233130.0", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "quantity": 8, + "unit": { + "name": "Miles", + "id": "SMI", + "scheme": "UNCEFACT", + "value": { + "amount": 137000, + "currency": "GBP" + } + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ] } - } - ], - "dateSigned": "2015-06-10T14:23:12Z", - "documents": [ - { - "id": "0008", - "documentType": "contractSigned", - "title": "Signed Contract", - "description": "The Signed Contract for Cycle Path Construction", - "url": "http://example.com/contracts/ocds-213czf-000-00001", - "datePublished": "2015-06-10T16:43:12Z", - "format": "application/pdf", - "language": "en" - } - ] - } - ] - } + ], + "documents": [ + { + "id": "8.0", + "documentType": "contractSigned", + "title": "Signed Contract", + "description": "The Signed Contract for Cycle Path Construction", + "url": "http://example.com/contracts/ocds-213czf-000-00001", + "datePublished": "2015-06-10T16:43:12Z", + "format": "application/pdf", + "language": "en" + } + ] + } + ] + } ] -} - - +} \ No newline at end of file diff --git a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-06-implementation.json b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-06-implementation.json index 9ba281db3..f5c7b0bd2 100755 --- a/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-06-implementation.json +++ b/persistence-mongodb/src/test/resources/json/fictional-example/ocds-213czf-000-00001-06-implementation.json @@ -1,238 +1,236 @@ { - "uri":"http://standard.open-contracting.org/examples/releases/ocds-213czf-000-00001-05-contract.json", - "publishedDate":"2011-01-10T09:30:00Z", + "version": "1.1", + "uri": "https://github.com/open-contracting/sample-data/raw/1.1/fictional-example/ocds-213czf-000-00001-06-implementation.json", + "publishedDate": "2011-01-10T09:30:00Z", "publisher": { - "scheme": "GB-COH", - "uid": "09506232", "name": "Open Data Services Co-operative Limited", - "uri": "http://standard.open-contracting.org/examples/" + "scheme": "GB-COH", + "uid": "9506232.0", + "uri": "http://data.companieshouse.gov.uk/doc/company/09506232" }, - "license":"http://opendatacommons.org/licenses/pddl/1.0/", - "publicationPolicy":"https://github.com/open-contracting/sample-data/", - "releases":[{ - "language": "en", - "ocid": "ocds-213czf-000-00001", - "id": "ocds-213czf-000-00001-06-implementation", - "date": "2011-01-10T09:30:00Z", - "tag": ["implementation"], - "initiationType": "tender", - "buyer": { - "identifier": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" - }, - "name": "London Borough of Barnet", - "address": { - "streetAddress": "4, North London Business Park, Oakleigh Rd S", - "locality": "London", - "region": "London", - "postalCode": "N11 1NP", - "countryName": "United Kingdom" + "license": "http://opendatacommons.org/licenses/pddl/1.0/", + "publicationPolicy": "https://github.com/open-contracting/sample-data/", + "releases": [ + { + "ocid": "ocds-213czf-000-00001", + "id": "ocds-213czf-000-00001-06-implementation", + "language": "en", + "date": "2011-01-10T09:30:00Z", + "initiationType": "tender", + "tag": [ + "implementation" + ], + "parties": [ + { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet", + "roles": [ + "buyer" + ], + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet" + }, + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" + } + } + ], + "buyer": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" }, - "contactPoint": { - "name": "Procurement Team", - "email": "procurement-team@example.com", - "telephone": "01234 345 346", - "faxNumber": "01234 345 345", - "url": "http://example.com/contact/" - } - }, - "awards": [ - { - "id": "ocds-213czf-000-00001-award-01", - "title": "Award of contract to build new cycle lanes in the centre of town.", - "description": "AnyCorp Ltd has been awarded the contract to build new cycle lanes in the centre of town.", - "status": "active", - "date": "2010-05-10T10:30:00Z", - "value": { - "amount": 11000000, - "currency": "GBP" - }, - "suppliers": [ - { - "identifier": { - "scheme": "GB-COH", - "id": "1234567844", - "legalName": "AnyCorp Ltd", - "uri": "http://www.anycorp.example" - }, - "additionalIdentifiers": [ - { - "scheme": "GB-VAT", - "id": "987654321" - } - ], - "name": "AnyCorp Cycle Provision", - "address": { - "streetAddress": "100 Anytown Lane", - "locality": "Anytown", - "region": "AnyCounty", - "postalCode": "AN1 1TN", - "countryName": "United Kingdom" - }, - "contactPoint": { - "name": "Contracts Team", - "email": "contracts@anycorp.example", - "telephone": "12345 456 343", - "faxNumber": "12345 456 343" + "awards": [ + { + "id": "ocds-213czf-000-00001-award-01", + "title": "Award of contract to build new cycle lanes in the centre of town.", + "description": "AnyCorp Ltd has been awarded the contract to build new cycle lanes in the centre of town.", + "status": "active", + "date": "2010-05-10T10:30:00Z", + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "suppliers": [ + { + "id": "GB-COH-11111111", + "name": "AnyCorp Cycle Provision" } - } - ], - "items": [ - { - "id": "0001", - "description": "string", - "classification": { - "scheme": "CPV", - "id": "45233130", - "description": "Construction work for highways", - "uri": "http://cpv.data.ac.uk/code-45233130" - }, - "additionalClassifications": [ - { + ], + "contractPeriod": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-06-30T23:59:00Z", + "maxExtentDate": "2011-12-31T23:59:00Z" + }, + "items": [ + { + "id": "1.0", + "description": "Cycle lane improvements", + "classification": { "scheme": "CPV", - "id": "45233162-2", - "description": "Cycle path construction work", - "uri": "http://cpv.data.ac.uk/code-45233162.html" - } - ], - "quantity": 8, - "unit": { - "name": "Miles", - "value": { - "amount": 137000, - "currency": "GBP" - } + "id": "45233130.0", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "quantity": 8, + "unit": { + "name": "Miles", + "id": "SMI", + "scheme": "UNCEFACT", + "value": { + "amount": 137000, + "currency": "GBP" + } + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ] } - } - ], - "contractPeriod": { - "startDate": "2010-07-01T00:00:00Z", - "endDate": "2011-08-01T23:59:00Z" - }, - "documents": [ - { - "id": "0007", - "documentType": "notice", - "title": "Award notice", - "description": "Award of contract to build new cycle lanes in the centre of town to AnyCorp Ltd.", - "url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html", - "datePublished": "2010-05-10T10:30:00Z", - "format": "text/html", - "language": "en" - } - ] - } - ], - - "contracts": [ - { - "id": "ocds-213czf-000-00001-contract-01", - "awardID": "ocds-213czf-000-00001-award-01", - "title": "Contract to build new cycle lanes in the centre of town.", - "description": "Contract monitoring for cycle lane construction.", - "status": "active", - "period": { - "startDate": "2010-07-01T00:00:00Z", - "endDate": "2011-08-01T23:59:00Z" - }, - "value": { - "amount": 11000000, - "currency": "GBP" - }, - "items": [ - { - "id": "0001", - "description": "string", - "classification": { - "scheme": "CPV", - "id": "45233130", - "description": "Construction work for highways", - "uri": "http://cpv.data.ac.uk/code-45233130" - }, - "additionalClassifications": [ - { - "scheme": "CPV", - "id": "45233162-2", - "description": "Cycle path construction work", - "uri": "http://cpv.data.ac.uk/code-45233162.html" - } - ], - "quantity": 8, - "unit": { - "name": "Miles", - "value": { - "amount": 137000, - "currency": "GBP" - } + ], + "documents": [ + { + "id": "7.0", + "documentType": "awardNotice", + "title": "Award notice", + "description": "Award of contract to build new cycle lanes in the centre of town to AnyCorp Ltd.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html", + "datePublished": "2010-05-10T10:30:00Z", + "format": "text/html", + "language": "en" } - } - ], - "dateSigned": "2015-06-10T14:23:12Z", - "documents": [ - { - "id": "0009", - "documentType": "physicalProcessReport", - "title": "Progress report", - "description": "Physical progress report for cycle path construction", - "url": "http://example.com/reports/ocds-213czf-000-00001/cycle-path-01.pdf", - "datePublished": "2010-12-15T15:34:02Z", - "format": "application/pdf", - "language": "en" - } - ], - "implementation": { - "transactions": [ + ] + } + ], + "contracts": [ + { + "id": "ocds-213czf-000-00001-contract-01", + "awardID": "ocds-213czf-000-00001-award-01", + "title": "Contract to build new cycle lanes in the centre of town.", + "description": "A contract has been signed between the Council and AnyCorp Ltd for construction of new cycle lanes in the centre of town.", + "status": "active", + "period": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-06-30T23:59:00Z", + "maxExtentDate": "2011-12-31T23:59:00Z" + }, + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "dateSigned": "2010-06-10T14:23:12Z", + "items": [ { - "id": "ocds-213czf-000-00001-1", - "source": "https://openspending.org/uk-barnet-spending/", - "date": "2010-08-01T00:00:00Z", - "amount": { - "amount": 500000, - "currency": "GBP" - }, - "providerOrganization": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" + "id": "1.0", + "description": "Cycle lane improvements", + "classification": { + "scheme": "CPV", + "id": "45233130.0", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" }, - "receiverOrganization": { - "scheme": "GB-COH", - "id": "1234567844", - "legalName": "AnyCorp Ltd", - "uri": "http://www.anycorp.example" + "quantity": 8, + "unit": { + "name": "Miles", + "id": "SMI", + "scheme": "UNCEFACT", + "value": { + "amount": 137000, + "currency": "GBP" + } }, - "uri": "https://openspending.org/uk-barnet-spending/transaction/asd9235qaghvs1059620ywhgai" - }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ] + } + ], + "relatedProcesses": [ { - "id": "ocds-213czf-000-00001-2", - "source": "https://openspending.org/uk-barnet-spending/", - "date": "2010-10-01T00:00:00Z", - "amount": { - "amount": 100000, - "currency": "GBP" - }, - "providerOrganization": { - "scheme": "GB-LAC", - "id": "E09000003", - "legalName": "London Borough of Barnet", - "uri": "http://www.barnet.gov.uk/" - }, - "receiverOrganization": { - "scheme": "GB-COH", - "id": "1234567844", - "legalName": "AnyCorp Ltd", - "uri": "http://www.anycorp.example" - }, - "uri": "https://openspending.org/uk-barnet-spending/transaction/asd9235qaghvs105962as0012" + "id": "1.0", + "title": "Tender for the hire of works barriers and signage", + "relationship": [ + "subContract" + ], + "scheme": "ocid", + "identifier": "ocds-213czf-000-00002", + "uri": "http://www.example.com/openContracting/ocds-213czf-000-00002" } - ] + ], + "implementation": { + "transactions": [ + { + "id": "ocds-213czf-000-00001-1", + "source": "https://openspending.org/uk-barnet-spending/", + "date": "2010-08-01T00:00:00Z", + "value": { + "amount": 50000, + "currency": "GBP" + }, + "payer": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" + }, + "payee": { + "id": "GB-COH-11111111", + "name": "AnyCorp Cycle Provision" + }, + "uri": "https://openspending.org/uk-barnet-spending/transaction/asd9235qaghvs1059620ywhgai" + }, + { + "id": "ocds-213czf-000-00001-2", + "source": "https://openspending.org/uk-barnet-spending/", + "date": "2010-10-01T00:00:00Z", + "value": { + "amount": 10000, + "currency": "GBP" + }, + "payer": { + "id": "GB-LAC-E09000003", + "name": "London Borough of Barnet" + }, + "payee": { + "id": "GB-COH-11111111", + "name": "AnyCorp Cycle Provision" + }, + "uri": "https://openspending.org/uk-barnet-spending/transaction/asd9235qaghvs105962as0012" + } + ], + "documents": [ + { + "id": "9.0", + "documentType": "physicalProgressReport", + "title": "Progress report", + "description": "Physical progress report for cycle path construction", + "url": "http://example.com/reports/ocds-213czf-000-00001/cycle-path-01.pdf", + "datePublished": "2010-12-15T15:34:02Z", + "format": "application/pdf", + "language": "en" + } + ] + } } - } - ] - }] -} - + ] + } + ] +} \ No newline at end of file diff --git a/persistence-mongodb/src/test/resources/json/release-excel-export.json b/persistence-mongodb/src/test/resources/json/release-excel-export.json index 548cd3e70..f4118413b 100644 --- a/persistence-mongodb/src/test/resources/json/release-excel-export.json +++ b/persistence-mongodb/src/test/resources/json/release-excel-export.json @@ -280,7 +280,7 @@ }, "planning": { "budget": { - "source": "Highways and Pavements Budget", + "source": "http://abcdefg.ro", "id": "6801ad388f3a38b7740dde20108c58b35984ee91", "description": "Budget allocation for highway maintenance, aligned with 2015 strategic plan. ", "amount": { diff --git a/persistence-mongodb/src/test/resources/test.properties b/persistence-mongodb/src/test/resources/test.properties index 31c200f84..bffee7790 100644 --- a/persistence-mongodb/src/test/resources/test.properties +++ b/persistence-mongodb/src/test/resources/test.properties @@ -1,6 +1,3 @@ -local.mongo.port=27018 - -spring.mongodb.embedded.version=3.4.4 # LOGGING logging.level.root=warn @@ -11,3 +8,5 @@ logging.level.org.springframework.transaction=error logging.level.org.springframework.test=error logging.level.org.springframework.web=error logging.level.org.hibernate=error + +spring.data.mongodb.port=0 \ No newline at end of file diff --git a/persistence/.gitignore b/persistence/.gitignore index a60d6c4e3..eec3e741a 100644 --- a/persistence/.gitignore +++ b/persistence/.gitignore @@ -7,3 +7,4 @@ /derby.log /.checkstyle /.factorypath +/ehcache-diskstore/ diff --git a/persistence/pom.xml b/persistence/pom.xml index c095dc27e..c505b6889 100644 --- a/persistence/pom.xml +++ b/persistence/pom.xml @@ -22,6 +22,8 @@ 5.0.0.GA 2.6.11 0.3.5-06032016 + 3.2.1 + 20.0 @@ -41,7 +43,7 @@ org.springframework.boot spring-boot-starter-tomcat - + org.springframework.boot spring-boot-starter-data-rest @@ -194,6 +196,35 @@ + + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + + + + com.google.guava + guava + ${guava.version} + + + + commons-beanutils + commons-beanutils + + + + org.apache.poi + poi + ${poi.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + diff --git a/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java b/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java new file mode 100644 index 000000000..158baec3d --- /dev/null +++ b/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java @@ -0,0 +1,58 @@ +/** + * + */ +package org.devgateway.ocds.persistence.dao; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; +import org.devgateway.toolkit.persistence.dao.AbstractAuditableEntity; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.envers.Audited; + +/** + * @author mpostelnicu + */ +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +@Entity +@Audited +@Table(indexes = {@Index(columnList = "firstIndicator"), @Index(columnList = "secondIndicator")}) +public class ColorIndicatorPair extends AbstractAuditableEntity implements Serializable { + + private String firstIndicator; + + private String secondIndicator; + + private String color; + + public String getFirstIndicator() { + return firstIndicator; + } + + public void setFirstIndicator(String firstIndicator) { + this.firstIndicator = firstIndicator; + } + + public String getSecondIndicator() { + return secondIndicator; + } + + public void setSecondIndicator(String secondIndicator) { + this.secondIndicator = secondIndicator; + } + + @Override + public AbstractAuditableEntity getParent() { + return null; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java b/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java new file mode 100644 index 000000000..d77940bc7 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.persistence.repository; + +import java.util.List; +import org.devgateway.ocds.persistence.dao.ColorIndicatorPair; +import org.devgateway.toolkit.persistence.repository.category.TextSearchableRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author mpostelnicu + */ +@Transactional +@RepositoryRestResource +public interface ColorIndicatorPairRepository extends TextSearchableRepository { + + @Override + List findAll(); + + @Override + Page findAll(Pageable pageable); + + @Override + @RestResource(exported = false) + @PreAuthorize("hasRole('ROLE_ADMIN')") + void delete(Long id); + + @Query("select e from #{#entityName} e where (firstIndicator=:firstIndicator and " + + "secondIndicator=:secondIndicator ) or (firstIndicator=:secondIndicator and " + + "secondIndicator=:firstIndicator)") + ColorIndicatorPair findByFirstIndicatorAndSecondIndicator(@Param("firstIndicator") String firstIndicator, + @Param("secondIndicator") String secondIndicator); + + @Override + @Query("select e from #{#entityName} e where lower(e.firstIndicator) like %:txt% OR " + + "lower(e.secondIndicator) like %:txt%") + @PreAuthorize("hasRole('ROLE_ADMIN')") + Page searchText(@Param("txt") String txt, Pageable page); + + @Override + @RestResource(exported = false) + @PreAuthorize("hasRole('ROLE_ADMIN')") + void deleteAll(); + + @RestResource(exported = true) + @Override + ColorIndicatorPair getOne(Long id); + +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java index 593d8d805..0f9c9c6bc 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java @@ -21,6 +21,12 @@ public class AdminSettings extends AbstractAuditableEntity implements Serializab private Boolean rebootServer = false; + private String adminEmail = null; + + private Boolean enableDailyAutomatedImport = false; + + private String importFilesPath = null; + /** * This disables the security of /api/ endpoints, should be used for demo purposes only */ @@ -54,4 +60,29 @@ public Boolean getDisableApiSecurity() { public void setDisableApiSecurity(Boolean disableApiSecurity) { this.disableApiSecurity = disableApiSecurity; } + + + public String getAdminEmail() { + return adminEmail; + } + + public void setAdminEmail(String adminEmail) { + this.adminEmail = adminEmail; + } + + public Boolean getEnableDailyAutomatedImport() { + return enableDailyAutomatedImport; + } + + public void setEnableDailyAutomatedImport(Boolean enableDailyAutomatedImport) { + this.enableDailyAutomatedImport = enableDailyAutomatedImport; + } + + public String getImportFilesPath() { + return importFilesPath; + } + + public void setImportFilesPath(String importFilesPath) { + this.importFilesPath = importFilesPath; + } } diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Person.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Person.java index fbdcc19fb..283dd9c41 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Person.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Person.java @@ -15,6 +15,7 @@ import org.devgateway.ocds.persistence.dao.UserDashboard; import org.devgateway.toolkit.persistence.dao.categories.Group; import org.devgateway.toolkit.persistence.dao.categories.Role; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.envers.Audited; @@ -38,12 +39,16 @@ public class Person extends AbstractAuditableEntity implements Serializable, UserDetails, Labelable { private static final long serialVersionUID = 109780377848343674L; + @ExcelExport private String username; + @ExcelExport private String firstName; + @ExcelExport private String lastName; + @ExcelExport private String email; @JsonIgnore @@ -63,10 +68,10 @@ public class Person extends AbstractAuditableEntity implements Serializable, Use @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @ManyToOne(fetch = FetchType.EAGER) private Group group; - + @ManyToOne(fetch = FetchType.EAGER) private UserDashboard defaultDashboard; - + @ManyToMany(fetch = FetchType.EAGER, mappedBy = "users") private Set dashboards = new HashSet<>(); @@ -90,7 +95,7 @@ public class Person extends AbstractAuditableEntity implements Serializable, Use @ManyToMany(fetch = FetchType.EAGER) @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) private List roles; - + public Boolean getEnabled() { return enabled; } @@ -298,7 +303,7 @@ public void setDefaultDashboard(UserDashboard defaultDashboard) { @Override public void setLabel(String label) { // TODO Auto-generated method stub - + } @Override diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java index 720b694b3..a1fda847d 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java @@ -10,7 +10,7 @@ * Development Gateway - initial API and implementation *******************************************************************************/ /** - * + * */ package org.devgateway.toolkit.persistence.dao; @@ -29,6 +29,7 @@ import org.devgateway.toolkit.persistence.dao.categories.Group; import org.devgateway.toolkit.persistence.dao.categories.Role; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.envers.Audited; @@ -44,20 +45,25 @@ public class TestForm extends AbstractAuditableEntity implements Serializable { private static final long serialVersionUID = 1L; + @ExcelExport private String textField; + @ExcelExport @Column(length = DBConstants.MAX_DEFAULT_TEXT_LENGTH) private String textArea; @Column(length = DBConstants.MAX_DEFAULT_TEXT_LENGTH) private String summernote; + @ExcelExport private Boolean checkbox; private Boolean checkboxPicker; private Boolean checkboxToggle; + private String colorPicker; + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @ManyToOne private Group entitySelect; @@ -182,4 +188,12 @@ public Group getPreloadedEntitySelect() { public void setPreloadedEntitySelect(final Group preloadedEntitySelect) { this.preloadedEntitySelect = preloadedEntitySelect; } + + public String getColorPicker() { + return colorPicker; + } + + public void setColorPicker(final String colorPicker) { + this.colorPicker = colorPicker; + } } diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheet.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheet.java new file mode 100644 index 000000000..9e947f3e4 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheet.java @@ -0,0 +1,212 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.poi.common.usermodel.HyperlinkType; +import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.Hyperlink; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * Class that prepares the default Styles and Fonts for Excel cells. + * + * @author idobre + * @since 13/11/2017 + */ +public abstract class AbstractExcelSheet implements ExcelSheet { + protected final Workbook workbook; + + private Font dataFont; + + private Font headerFont; + + private Font linkFont; + + private final CellStyle dataStyleCell; + + private final CellStyle headerStyleCell; + + private final CellStyle linkStyleCell; + + private final CreationHelper createHelper; + + // declare only one cell object reference + private Cell cell; + + public AbstractExcelSheet(final Workbook workbook) { + this.workbook = workbook; + + // get the styles from workbook without creating them again (by default the workbook has already 1 style) + if (workbook.getNumCellStyles() > 1) { + this.dataStyleCell = workbook.getCellStyleAt((short) 1); + this.headerStyleCell = workbook.getCellStyleAt((short) 2); + this.linkStyleCell = workbook.getCellStyleAt((short) 3); + } else { + // init the fonts and styles + this.dataFont = this.workbook.createFont(); + this.dataFont.setFontHeightInPoints((short) 12); + this.dataFont.setFontName("Times New Roman"); + this.dataFont.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); + + this.headerFont = this.workbook.createFont(); + this.headerFont.setFontHeightInPoints((short) 14); + this.headerFont.setFontName("Times New Roman"); + this.headerFont.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); + this.headerFont.setBold(true); + + this.linkFont = this.workbook.createFont(); + this.linkFont.setFontHeightInPoints((short) 12); + this.linkFont.setFontName("Times New Roman"); + // by default hyperlinks are blue and underlined + this.linkFont.setColor(HSSFColor.HSSFColorPredefined.BLUE.getIndex()); + this.linkFont.setUnderline(Font.U_SINGLE); + + this.dataStyleCell = this.workbook.createCellStyle(); + this.dataStyleCell.setAlignment(HorizontalAlignment.LEFT); + this.dataStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); + this.dataStyleCell.setWrapText(true); + this.dataStyleCell.setFont(this.dataFont); + + this.headerStyleCell = this.workbook.createCellStyle(); + this.headerStyleCell.setAlignment(HorizontalAlignment.CENTER); + this.headerStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); + this.headerStyleCell.setWrapText(true); + this.headerStyleCell.setFont(this.headerFont); + + this.linkStyleCell = this.workbook.createCellStyle(); + this.linkStyleCell.setAlignment(HorizontalAlignment.LEFT); + this.linkStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); + this.linkStyleCell.setWrapText(true); + this.linkStyleCell.setFont(this.linkFont); + } + + this.createHelper = workbook.getCreationHelper(); + } + + /** + * Creates a cell and tries to determine it's type based on the value type. + * + * There is only one Cell object otherwise the Heap Space will fill really quickly. + * + * @param value + * @param row + * @param column + */ + @Override + public void writeCell(final Object value, final Row row, final int column) { + // try to determine the cell type based on the object value + // if nothing matches then use 'CellType.STRING' as type and call the object toString() function. + // * don't create any cell if the value is null (Cell.CELL_TYPE_BLANK) + // * do nothing if we have an empty List/Set instead of display empty brackets like [ ] + if (value != null && !((value instanceof List || value instanceof Set) && ((Collection) value).isEmpty())) { + if (value instanceof String) { + cell = row.createCell(column, CellType.STRING); + cell.setCellValue((String) value); + } else { + if (value instanceof Integer) { + cell = row.createCell(column, CellType.NUMERIC); + cell.setCellValue((Integer) value); + } else { + if (value instanceof BigDecimal) { + cell = row.createCell(column, CellType.NUMERIC); + cell.setCellValue(((BigDecimal) value).doubleValue()); + } else { + if (value instanceof Boolean) { + cell = row.createCell(column, CellType.BOOLEAN); + cell.setCellValue(((Boolean) value) ? "Yes" : "No"); + } else { + if (value instanceof Date) { + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); + + cell = row.createCell(column, CellType.STRING); + cell.setCellValue(sdf.format((Date) value)); + } else { + cell = row.createCell(column, CellType.STRING); + cell.setCellValue(value.toString()); + } + } + } + } + } + + cell.setCellStyle(dataStyleCell); + } else { + // create a CellType.BLANK + row.createCell(column); + } + } + + /** + * Create a header cell with a particular style. + * + * @param value + * @param row + * @param column + */ + protected void writeHeaderCell(final Object value, final Row row, final int column) { + this.writeCell(value, row, column); + cell.setCellStyle(headerStyleCell); + } + + /** + * Creates a cell that is a link to another sheet in the document {@link HyperlinkType#DOCUMENT}. + * + * @param value + * @param row + * @param column + * @param sheetName + * @param rowNumber + */ + public void writeCellLink(final Object value, final Row row, final int column, + final String sheetName, final int rowNumber) { + this.writeCell(value, row, column); + final Hyperlink link = createHelper.createHyperlink(HyperlinkType.DOCUMENT); + + // always point to first column A in excel file + link.setAddress("'" + sheetName + "'!A" + rowNumber); + cell.setHyperlink(link); + cell.setCellStyle(linkStyleCell); + } + + /** + * Create a new row and set the default height (different heights for headers and data rows). + * + * @param sheet + * @param rowNumber + * @return Row + */ + protected Row createRow(final Sheet sheet, final int rowNumber) { + final Row row = sheet.createRow(rowNumber); + + if (rowNumber < 1) { + row.setHeight((short) 800); // 40px (800 / 10 / 2) + } else { + row.setHeight((short) 600); // 30px (600 / 10 / 2) + } + + return row; + } + + /** + * Get the last 'free' cell of a {@link Row}. + * + * @param row + */ + protected int getFreeColl(final Row row) { + return row.getLastCellNum() == -1 ? 0 : row.getLastCellNum(); + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFieldService.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFieldService.java new file mode 100644 index 000000000..5fdc38dc3 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFieldService.java @@ -0,0 +1,196 @@ +package org.devgateway.toolkit.persistence.excel; + +import com.google.common.collect.Lists; +import org.apache.log4j.Logger; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; +import org.devgateway.toolkit.persistence.excel.info.ClassFields; +import org.devgateway.toolkit.persistence.excel.info.ClassFieldsDefault; +import org.devgateway.toolkit.persistence.excel.info.ClassFieldsExcelExport; +import org.springframework.data.domain.Persistable; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * @author idobre + * @since 10/11/2017 + */ +public final class ExcelFieldService { + private static final Logger logger = Logger.getLogger(ExcelFieldService.class); + + private static Map fieldsTypeCache; + + private static Map fieldsClassCache; + + private static Map> fieldsCache; + + + static { + fieldsTypeCache = new HashMap<>(); + fieldsClassCache = new HashMap<>(); + fieldsCache = new HashMap<>(); + } + + /** + * Utility classes should not have a public or default constructor. + */ + private ExcelFieldService() { + + } + + /** + * Return the {@link FieldType} of a Field + * This is used to determine the writing strategy for this particular field. + * + * @param field + * @return {@link FieldType} + */ + public static FieldType getFieldType(final Field field) { + FieldType fieldType; + + if (fieldsTypeCache.get(field) != null) { + fieldType = fieldsTypeCache.get(field); + } else { + fieldType = FieldType.basic; // default field type + final Class fieldClass = getFieldClass(field); + + // first we check if we have a basic type + if (FieldType.BASICTYPES.contains(fieldClass) || fieldClass.isEnum()) { + fieldType = FieldType.basic; + } else { + final ExcelExport excelExport = field.getAnnotation(ExcelExport.class); + if (excelExport != null) { + fieldType = FieldType.object; + + // if we just want to export a field (toString) we consider it a basic type. + if (excelExport.justExport()) { + fieldType = FieldType.basic; + } + + // check if we want to export the object in a separate sheet + if (excelExport.separateSheet()) { + fieldType = FieldType.objectSeparateSheet; + } + } + } + + fieldsTypeCache.put(field, fieldType); + } + + return fieldType; + } + + + /** + * Return the Class of a field. + * + * @param field + * @return {@link Class} + */ + public static Class getFieldClass(final Field field) { + Class fieldClass = null; + + if (fieldsClassCache.get(field) != null) { + fieldClass = fieldsClassCache.get(field); + } else { + if (isCollection(field)) { + final ParameterizedType genericListType = (ParameterizedType) field.getGenericType(); + try { + fieldClass = Class.forName(genericListType.getActualTypeArguments()[0].getTypeName()); + } catch (ClassNotFoundException e) { + logger.error(e); + } + } else { + fieldClass = field.getType(); + } + + fieldsClassCache.put(field, fieldClass); + } + + return fieldClass; + } + + /** + * Returns an Iterator with the Fields of a Class. + * The fields are filtered with the {@link ClassFieldsExcelExport} class. + * + * @param clazz + * @return {@link Iterator} + */ + public static Iterator getFields(final Class clazz) { + final Iterator fields; + + if (fieldsCache.get(clazz) != null) { + final List fieldsList = fieldsCache.get(clazz); + fields = fieldsList.iterator(); + } else { + final ClassFields classFields = new ClassFieldsExcelExport(new ClassFieldsDefault(clazz, true)); + fields = classFields.getFields(); + + fieldsCache.put(clazz, Lists.newArrayList(classFields.getFields())); + } + + return fields; + } + + /** + * Get the ID for an Entity - {@link Persistable}. + * + * Return -1 if everything wrong happened. + * + * @param object + * @return Entity ID + */ + public static Long getObjectID(final Object object) { + Long objectId = Long.valueOf(-1); + + if (object == null || !(Persistable.class.isAssignableFrom(object.getClass()))) { + return Long.valueOf(-1); + } + + try { + final Method idMethod = object.getClass().getMethod("getId"); + if (idMethod != null) { + objectId = (Long) idMethod.invoke(object); + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.error(e); + } + + return objectId; + } + + /** + * Returns the name that we should use as a header for this {@link Field}. + * + * @param field + */ + public static String getFieldName(final Field field) { + final ExcelExport excelExport = field.getAnnotation(ExcelExport.class); + if (excelExport != null) { + if (!excelExport.name().isEmpty()) { + return excelExport.name(); + } + } + + return field.getName(); + } + + /** + * Check if a {@link Field} Type is a Collection. + * + * @param field + * @return {@link Boolean} + */ + public static Boolean isCollection(final Field field) { + return field.getType().equals(java.util.Collection.class) + || field.getType().equals(java.util.Set.class) + || field.getType().equals(java.util.List.class); + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFile.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFile.java new file mode 100644 index 000000000..5f6e2d11d --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFile.java @@ -0,0 +1,18 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.poi.ss.usermodel.Workbook; + +/** + * ExcelFile Type - it should transform a list of Objects into a Workbook. + * + * @author idobre + * @since 13/11/2017 + */ +public interface ExcelFile { + /** + * Create an Workbook that can be exported. + * + * @return {@link Workbook} + */ + Workbook createWorkbook(); +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefault.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefault.java new file mode 100644 index 000000000..b2e7521ae --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefault.java @@ -0,0 +1,44 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; + +import java.util.List; + +/** + * Default implementation of the {@link ExcelFile} type. + * + * @author idobre + * @since 13/11/2017 + */ +public class ExcelFileDefault implements ExcelFile { + private final List objects; + + private final Workbook workbook; + + public ExcelFileDefault(final List objects) { + Validate.notNull(objects, "The list of objects can't be null!"); + Validate.noNullElements(objects, "The list of objects can't contain null elements!"); + + this.objects = objects; + + // create the excel file + this.workbook = new SXSSFWorkbook(100); + } + + @Override + public Workbook createWorkbook() { + // don't do anything if the list of objects is empty, just display the error message. + if (objects.isEmpty()) { + final ExcelSheet excelSheet = new ExcelSheetDefault(this.workbook, "no data"); + excelSheet.emptySheet(); + } else { + final Class clazz = this.objects.get(0).getClass(); + final ExcelSheet excelSheet = new ExcelSheetDefault(this.workbook, clazz.getSimpleName().toLowerCase()); + excelSheet.writeSheet(clazz, objects); + } + + return workbook; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheet.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheet.java new file mode 100644 index 000000000..41a33a155 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheet.java @@ -0,0 +1,62 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.poi.ss.usermodel.Row; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; + +import java.util.List; + +/** + * Create an Excel Sheet representation of an Object of type T. + * In this process it's possible to create a new Excel Sheet if + * some fields are annotated with {@link ExcelExport#separateSheet = true} + * + * @author idobre + * @since 13/11/2017 + */ +public interface ExcelSheet { + /** + * Write the value into specified {@link Row} and column number. + * + * @param value + * @param row + * @param column + */ + void writeCell(Object value, Row row, int column); + + /** + * Write the object into specified {@link Row}. + * We need to also know the {@link Class} of the Object + * since we will export null objects as well but with empty cells. + * + * @param clazz + * @param object + * @param row + */ + void writeRow(Class clazz, Object object, Row row); + + /** + * Write the received list of objects into an excel sheet. + * We need to also know the {@link Class} of the Object(s) + * since we will export null objects as well but with empty cells. + * + * @param clazz + * @param objects + */ + void writeSheet(Class clazz, List objects); + + /** + * Write the objects and return the first row index. + * Used to (possible) create a document link between sheets. + */ + int writeSheetGetLink(Class clazz, List objects); + + /** + * Returns the Sheet name. + */ + String getExcelSheetName(); + + /** + * Just create an empty excel sheet. + */ + void emptySheet(); +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheetDefault.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheetDefault.java new file mode 100644 index 000000000..bd53dc239 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheetDefault.java @@ -0,0 +1,354 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.log4j.Logger; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.StringJoiner; + +/** + * @author idobre + * @since 13/11/2017 + */ +public class ExcelSheetDefault extends AbstractExcelSheet { + private static final Logger logger = Logger.getLogger(ExcelSheetDefault.class); + + private static final String PARENTSHEET = "parentSheet"; + private static final String PARENTID = "parentID"; + private static final String PARENTROWNUMBER = "parentRowNumber"; + + private final Sheet excelSheet; + + private final String excelSheetName; + + // Use this field to identify the header prefix for children objects that are exported in the same sheet. + private final Stack headerPrefix; + + private Map parentInfo; + + // Boolean that identifies if the header was written already for this sheet. + private Boolean headerWasWritten = false; + + /** + * Constructor used to print an Object in a separate excel sheet. + * The sheet will be created based on object name that is exported. + * + * {@link #excelSheetName} would be the name of the excel + * and the recommended value is {@link Class#getSimpleName} toLowerCase. + * + * @param workbook + * @param excelSheetName + */ + public ExcelSheetDefault(final Workbook workbook, final String excelSheetName) { + super(workbook); + + this.excelSheetName = excelSheetName; + this.headerPrefix = new Stack<>(); + + // it's possible that this sheet was created by other object + if (workbook.getSheet(excelSheetName) == null) { + // create the main excel sheet + excelSheet = workbook.createSheet(excelSheetName); + + // freeze the header row + excelSheet.createFreezePane(0, 1); + + // create the first row that is used as a header + createRow(excelSheet, 0); + + // set a default width - this will increase the performance + excelSheet.setDefaultColumnWidth(35); + } else { + excelSheet = workbook.getSheet(excelSheetName); + } + } + + /** + * Constructor used to print an Object in a separate sheet + * but with the additional possibility to create a link back to Object's Parent sheet. + * + * @param workbook + * @param excelSheetName + * @param parentInfo + */ + ExcelSheetDefault(final Workbook workbook, final String excelSheetName, final Map parentInfo) { + this(workbook, excelSheetName); + + this.parentInfo = parentInfo; + } + + @Override + public void writeRow(final Class clazz, final Object object, final Row row) { + final Iterator fields = ExcelFieldService.getFields(clazz); + + // if we have a parent then the first row should be the parent name with a link. + if (getFreeColl(row) == 0 && parentInfo != null) { + String parentCell = (String) parentInfo.get(PARENTSHEET); + if (parentInfo.get(PARENTID) != null) { + parentCell += " - " + parentInfo.get(PARENTID); + } + writeHeaderLabel("Parent", row, 0); + writeCellLink(parentCell, row, 0, + (String) parentInfo.get(PARENTSHEET), (int) parentInfo.get(PARENTROWNUMBER)); + } + + while (fields.hasNext()) { + final Field field = fields.next(); + final FieldType fieldType = ExcelFieldService.getFieldType(field); + + try { + switch (fieldType) { + case basic: + int coll = getFreeColl(row); + writeHeaderLabel(field, row, coll); + writeCell(getFieldValue(field, object), row, coll); + + break; + + case object: + headerPrefix.push(ExcelFieldService.getFieldName(field)); + Class fieldClass = ExcelFieldService.getFieldClass(field); + + if (ExcelFieldService.isCollection(field)) { + final List flattenObjects = new ArrayList(); + flattenObjects.addAll((Collection) getFieldValue(field, object)); + writeRowFlattenObject(fieldClass, flattenObjects, row); + } else { + writeRow(fieldClass, getFieldValue(field, object), row); + } + + headerPrefix.pop(); + break; + + case objectSeparateSheet: + fieldClass = ExcelFieldService.getFieldClass(field); + + // add some informations about the parent + final Map info = new HashMap(); + info.put(PARENTSHEET, excelSheetName); + info.put(PARENTID, ExcelFieldService.getObjectID(object)); + info.put(PARENTROWNUMBER, row.getRowNum() + 1); + + final ExcelSheet objectSepareteSheet = new ExcelSheetDefault(workbook, + fieldClass.getSimpleName().toLowerCase(), info); + final List newObjects = new ArrayList(); + final Object value = getFieldValue(field, object); + + if (value != null) { + if (ExcelFieldService.isCollection(field)) { + newObjects.addAll((Collection) value); + } else { + newObjects.add(value); + } + } + + coll = getFreeColl(row); + writeHeaderLabel(field, row, coll); + final int rowNumber = objectSepareteSheet.writeSheetGetLink(fieldClass, newObjects); + if (rowNumber != -1) { + writeCellLink(field.getName(), row, coll, + objectSepareteSheet.getExcelSheetName(), rowNumber); + } else { + writeCell(null, row, coll); + } + + break; + + default: + logger.error("Undefined field type"); + break; + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.error(e); + } + } + } + + /** + * Function to flat export an array of objects. Example: + * [{ + * x: aaa, + * y: bbb, + * },{ + * x: ccc, + * y: ddd, + * }] + * + * Will be printed as: + * | x | y | + * | aaa ; bbb | ccc ; ddd | + * + * @param clazz + * @param objects + * @param row + */ + private void writeRowFlattenObject(final Class clazz, final List objects, final Row row) { + final Iterator fields = ExcelFieldService.getFields(clazz); + + while (fields.hasNext()) { + final Field field = fields.next(); + final FieldType fieldType = ExcelFieldService.getFieldType(field); + + try { + switch (fieldType) { + case basic: + final int coll = getFreeColl(row); + final StringJoiner flattenValue = new StringJoiner(" | "); + + for (final Object obj : objects) { + final Object value = getFieldValue(field, obj); + if (value != null) { + flattenValue.add(value.toString()); + } + } + + writeHeaderLabel(field, row, coll); + writeCell(flattenValue.toString(), row, coll); + break; + + case object: + if (ExcelFieldService.isCollection(field)) { + logger.error("Unsupported operation for field: '" + field.getName() + "'! You can not " + + "flatten an array of objects that contains other array of objects"); + } else { + headerPrefix.push(ExcelFieldService.getFieldName(field)); + final Class fieldClass = ExcelFieldService.getFieldClass(field); + final List newObjects = new ArrayList(); + + for (Object obj : objects) { + final Object value = getFieldValue(field, obj); + if (value != null) { + newObjects.add(value); + } + } + + writeRowFlattenObject(fieldClass, newObjects, row); + headerPrefix.pop(); + } + + break; + + case objectSeparateSheet: + logger.error("Unsupported operation for field: '" + field.getName() + "'! You can not flatten " + + "an array of objects that contains objects that need to be printed in other sheet."); + break; + + default: + logger.error("Undefined field type"); + break; + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.error(e); + } + } + } + + @Override + public void writeSheet(final Class clazz, final List objects) { + for (Object obj : objects) { + int lastRow = excelSheet.getLastRowNum(); + final Row row = createRow(excelSheet, ++lastRow); + + writeRow(clazz, obj, row); + } + } + + @Override + public int writeSheetGetLink(final Class clazz, final List objects) { + if (objects == null || objects.isEmpty()) { + return -1; + } + + // check if this is first time when we created this sheet and skip header row + // also add 2 since the getLastRowNum() function is 0-based and Excel is 1-based + int lastRow = excelSheet.getLastRowNum() == 0 ? 2 : (excelSheet.getLastRowNum() + 2); + this.writeSheet(clazz, objects); + + return lastRow; + } + + /** + * Print an error message in case we have an empty sheet. + */ + public void emptySheet() { + final Row row; + if (excelSheet.getRow(0) == null) { + row = createRow(excelSheet, 0); + } else { + row = excelSheet.getRow(0); + } + writeHeaderCell("No objects returned.", row, 0); + } + + /** + * Compute the header prefix for the current object. + */ + private String getHeaderPrefix() { + if (headerPrefix.isEmpty()) { + return ""; + } + + final StringJoiner header = new StringJoiner("/"); + final Enumeration elements = headerPrefix.elements(); + while (elements.hasMoreElements()) { + header.add(elements.nextElement()); + } + + return header.toString() + "/"; + } + + /** + * Functions that check if the header cell is empty, if yes then add the label. + */ + private void writeHeaderLabel(final String label, final Row row, final int coll) { + if (!headerWasWritten) { + if (row.getRowNum() == 1) { // write the header only once. + final Row headerRow = excelSheet.getRow(0); + final Cell headerCell = headerRow.getCell(coll); + + if (headerCell == null) { + writeHeaderCell(label, headerRow, coll); + } + } else { + headerWasWritten = true; + } + } + } + + /** + * Functions that check if the header cell is empty, if yes then add the field label. + */ + private void writeHeaderLabel(final Field field, final Row row, final int coll) { + if (!headerWasWritten) { + writeHeaderLabel(getHeaderPrefix() + ExcelFieldService.getFieldName(field), row, coll); + } + } + + /** + * Return the value of a {@link Field} from an {@link Object}. + */ + private Object getFieldValue(final Field field, final Object object) + throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + return object == null || !PropertyUtils.isReadable(object, field.getName()) + ? null + : PropertyUtils.getProperty(object, field.getName()); + } + + @Override + public String getExcelSheetName() { + return excelSheetName; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/FieldType.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/FieldType.java new file mode 100644 index 000000000..e8d8c39b5 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/FieldType.java @@ -0,0 +1,51 @@ +package org.devgateway.toolkit.persistence.excel; + +import com.google.common.collect.ImmutableSet; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * Identify the type of a Field that will be used in the writing strategy. + * + * @author idobre + * @since 10/11/2017 + */ +public enum FieldType { + basic("basic"), + + object("object"), + + objectSeparateSheet("objectSeparateSheet"); + + private final String value; + + FieldType(final String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + /** + * This is not a complete list of primitive types in Java or their wrappers! + * Is used to quickly identify if a field is a 'simply' object that can be printed in a Cell + */ + public static final ImmutableSet> BASICTYPES = new ImmutableSet.Builder>() + .add(String.class) + .add(BigDecimal.class) + .add(Date.class) + .add(Integer.class) + .add(Long.class) + .add(Double.class) + .add(Float.class) + .add(Boolean.class) + .add(int.class) + .add(long.class) + .add(double.class) + .add(float.class) + .add(boolean.class) + .build(); +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/annotation/ExcelExport.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/annotation/ExcelExport.java new file mode 100644 index 000000000..73e2e4da5 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/annotation/ExcelExport.java @@ -0,0 +1,27 @@ +package org.devgateway.toolkit.persistence.excel.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation that indicates if a field should be exported to Excel Export. + * + * {@link #name} - field name that will be used in the Excel header + * {@link #separateSheet} - parameter that indicates if an object should be exported in a separate Excel Sheet + * {@link #justExport} - just export the Object usually using {@link #toString} method (without exporting it's + * children) + * + * @author idobre + * @since 10/11/2017 + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcelExport { + String value() default ""; + + String name() default ""; + + boolean separateSheet() default false; + + boolean justExport() default false; +} + diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFields.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFields.java new file mode 100644 index 000000000..b1e63c9a2 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFields.java @@ -0,0 +1,14 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import java.lang.reflect.Field; +import java.util.Iterator; + +/** + * Returns all the fields of a Class. + * + * @author idobre + * @since 10/11/2017 + */ +public interface ClassFields { + Iterator getFields(); +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefault.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefault.java new file mode 100644 index 000000000..269ca4d14 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefault.java @@ -0,0 +1,69 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * Default implementation of {@link ClassFields} interface that will return all declared fields of a class. + * + * @author idobre + * @since 10/11/2017 + */ +public final class ClassFieldsDefault implements ClassFields { + private final Class clazz; + + private final Boolean getInheritedFields; + + private Field[] declaredFields; + + public ClassFieldsDefault(final Class clazz, final Boolean getInheritedFields) { + this.clazz = clazz; + this.getInheritedFields = getInheritedFields; + } + + public ClassFieldsDefault(final Class clazz) { + this(clazz, false); + } + + @Override + public Iterator getFields() { + // cache declared fields of a class + if (declaredFields == null) { + if (getInheritedFields) { + declaredFields = getAllFields(clazz).toArray(new Field[getAllFields(clazz).size()]); + } else { + declaredFields = clazz.getDeclaredFields(); + } + } + + // filter some of the fields including this$0 used in inner classes + final Iterator fields = Arrays.stream(declaredFields) + .filter(field -> !field.getName().equals("serialVersionUID")) + .filter(field -> !field.getName().equals("this$0")) + .iterator(); + + return fields; + } + + /** + * Function used to get also the inherited fields. + * + * @param clazz + * @return + */ + private List getAllFields(final Class clazz) { + final List fields = new ArrayList<>(); + final Class superClazz = clazz.getSuperclass(); + + if (superClazz != null) { + fields.addAll(getAllFields(superClazz)); + } + + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + + return fields; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExport.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExport.java new file mode 100644 index 000000000..f5419314d --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExport.java @@ -0,0 +1,33 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; + +import java.lang.reflect.Field; +import java.util.Iterator; +import java.util.stream.StreamSupport; + +/** + * Decorator class used to obtain only Excel Exportable fields (the ones annotated with {@link ExcelExport}. + * + * @author idobre + * @since 10/11/2017 + */ +public final class ClassFieldsExcelExport implements ClassFields { + private final ClassFields original; + + public ClassFieldsExcelExport(final ClassFields classFields) { + this.original = classFields; + } + + @Override + public Iterator getFields() { + final Iterable originalFields = () -> this.original.getFields(); + + // return only classes that are annotated with @ExcelExport + final Iterator fields = StreamSupport.stream(originalFields.spliterator(), false) + .filter(field -> field.getAnnotation(ExcelExport.class) != null) + .iterator(); + + return fields; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/service/ExcelGeneratorService.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/service/ExcelGeneratorService.java new file mode 100644 index 000000000..835810ea7 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/service/ExcelGeneratorService.java @@ -0,0 +1,61 @@ +package org.devgateway.toolkit.persistence.excel.service; + +import org.apache.poi.ss.usermodel.Workbook; +import org.devgateway.toolkit.persistence.excel.ExcelFile; +import org.devgateway.toolkit.persistence.excel.ExcelFileDefault; +import org.devgateway.toolkit.persistence.repository.BaseJpaRepository; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +/** + * @author idobre + * @since 15/11/2017 + */ +@Service +@CacheConfig(keyGenerator = "genericExcelKeyGenerator", cacheNames = "excelExportCache") +@Cacheable +public class ExcelGeneratorService { + /** + * Method that returns a byte array with an excel export. + * + * @param jpaRepository - {@link JpaRepository} from where we will get the data + * @param spec - {@link Specification} for filtering the data + * @param pageable - {@link Pageable} for paginating the data + * + * @return byte[] + * @throws IOException + */ + public byte[] getExcelDownload(final BaseJpaRepository jpaRepository, + final Specification spec, + final Pageable pageable) throws IOException { + final List objects = jpaRepository.findAll(spec, pageable).getContent(); + final ExcelFile excelFile = new ExcelFileDefault(objects); + final Workbook workbook = excelFile.createWorkbook(); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + workbook.write(baos); + final byte[] bytes = baos.toByteArray(); + + return bytes; + } + + /** + * Return the number of records for this {@link Specification}. + * + * @param jpaRepository + * @param spec + * @return count + */ + public long count(final BaseJpaRepository jpaRepository, + final Specification spec) { + return jpaRepository.count(spec); + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/DatabaseConfiguration.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/DatabaseConfiguration.java index b8e0aa95f..6228a5c58 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/DatabaseConfiguration.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/DatabaseConfiguration.java @@ -1,25 +1,20 @@ -/******************************************************************************* +/** * Copyright (c) 2015 Development Gateway, Inc and others. - * + *

* All rights reserved. This program and the accompanying materials * are made available under the terms of the MIT License (MIT) * which accompanies this distribution, and is available at * https://opensource.org/licenses/MIT - * + *

* Contributors: * Development Gateway - initial API and implementation - *******************************************************************************/ + */ /** - * + * */ package org.devgateway.toolkit.persistence.spring; -import java.io.PrintWriter; -import java.net.InetAddress; -import java.util.Properties; - -import javax.naming.NamingException; - +import liquibase.integration.spring.SpringLiquibase; import org.apache.derby.drda.NetworkServerControl; import org.apache.log4j.Logger; import org.apache.tomcat.jdbc.pool.DataSource; @@ -33,6 +28,12 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.mock.jndi.SimpleNamingContextBuilder; +import javax.naming.NamingException; +import javax.persistence.EntityManagerFactory; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.util.Properties; + /** * @author mpostelnicu * @@ -85,7 +86,7 @@ public class DatabaseConfiguration { * toolkitDS/driver=org.apache.derby.jdbc.ClientDriver toolkitDS/user=app * toolkitDS/password=app * toolkitDS/url=jdbc:derby://localhost//derby/toolkit - * + * * @return */ @Bean @@ -106,11 +107,11 @@ public SimpleNamingContextBuilder jndiBuilder() { /** * Creates a {@link javax.sql.DataSource} based on Tomcat {@link DataSource} - * + * * @return */ @Bean - @DependsOn(value = { "derbyServer" }) + @DependsOn(value = {"derbyServer"}) public DataSource dataSource() { PoolProperties pp = new PoolProperties(); pp.setJmxEnabled(true); @@ -129,7 +130,7 @@ public DataSource dataSource() { /** * Graciously starts a Derby Database Server when the application starts up - * + * * @return * @throws Exception */ @@ -143,4 +144,12 @@ public NetworkServerControl derbyServer() throws Exception { return nsc; } + @Bean + public SpringLiquibaseRunner liquibaseAfterJPA(final SpringLiquibase springLiquibase, + final EntityManagerFactory entityManagerFactory) { + logger.info("Instantiating SpringLiquibaseRunner after initialization of entityManager using factory " + + entityManagerFactory); + return new SpringLiquibaseRunner(springLiquibase); + } + } diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java index af1e0e27a..900c4c770 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java @@ -13,12 +13,20 @@ import org.devgateway.ocds.persistence.dao.UserDashboard; import org.devgateway.ocds.persistence.repository.UserDashboardRepository; +import org.apache.catalina.startup.Tomcat; import org.devgateway.toolkit.persistence.dao.GenericPersistable; import org.devgateway.toolkit.persistence.repository.RoleRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.PropertySource; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -26,9 +34,8 @@ /** * Run this application only when you need access to Spring Data JPA but without * Wicket frontend - * - * @author mpostelnicu * + * @author mpostelnicu */ @SpringBootApplication @EnableJpaRepositories(basePackageClasses = { RoleRepository.class, UserDashboardRepository.class }) @@ -38,7 +45,23 @@ @ComponentScan("org.devgateway.toolkit") public class PersistenceApplication { + private Logger logger = LoggerFactory.getLogger(PersistenceApplication.class); + public static void main(final String[] args) { SpringApplication.run(PersistenceApplication.class, args); } + + @Bean + @Profile("!integration") + public TomcatEmbeddedServletContainerFactory tomcatFactory(@Qualifier("liquibaseAfterJPA") final + SpringLiquibaseRunner liquibaseAfterJPA) { + logger.info("Instantiating tomcat after initialization of " + liquibaseAfterJPA); + return new TomcatEmbeddedServletContainerFactory() { + @Override + protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(final Tomcat tomcat) { + tomcat.enableNaming(); + return super.getTomcatEmbeddedServletContainer(tomcat); + } + }; + } } \ No newline at end of file diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/SpringLiquibaseRunner.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/SpringLiquibaseRunner.java new file mode 100644 index 000000000..b027e0f14 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/SpringLiquibaseRunner.java @@ -0,0 +1,31 @@ +package org.devgateway.toolkit.persistence.spring; + +import liquibase.integration.spring.SpringLiquibase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Octavian Ciubotaru + */ +public class SpringLiquibaseRunner implements InitializingBean { + + private Logger logger = LoggerFactory.getLogger(SpringLiquibaseRunner.class); + + private final SpringLiquibase springLiquibase; + + public SpringLiquibaseRunner(final SpringLiquibase springLiquibase) { + this.springLiquibase = springLiquibase; + } + + @Override + public void afterPropertiesSet() throws Exception { + logger.info("Attempting to run liquibase second time"); + springLiquibase.afterPropertiesSet(); + } + + @Override + public String toString() { + return getClass().getName() + " (" + springLiquibase + ")"; + } +} \ No newline at end of file diff --git a/persistence/src/main/resources/ehcache.xml b/persistence/src/main/resources/ehcache.xml index bbfa22eb2..7a61a5469 100644 --- a/persistence/src/main/resources/ehcache.xml +++ b/persistence/src/main/resources/ehcache.xml @@ -1,89 +1,109 @@ - + - + - + - + - + + + - + - + + statistics="true" + overflowToDisk="true" + maxElementsOnDisk="10000000" + diskPersistent="true" + diskExpiryThreadIntervalSeconds="120" > + statistics="true" + overflowToDisk="true" + maxElementsOnDisk="10000000" + diskPersistent="true" + diskExpiryThreadIntervalSeconds="120" > + statistics="true" + overflowToDisk="true" + maxElementsOnDisk="10000000" + diskPersistent="true" + diskExpiryThreadIntervalSeconds="120" > - - - - - - - - - - - - - - + statistics="true" + overflowToDisk="true" + maxElementsOnDisk="10000000" + diskPersistent="true" + diskExpiryThreadIntervalSeconds="120" > \ No newline at end of file diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheetTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheetTest.java new file mode 100644 index 000000000..5dcd87fd0 --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheetTest.java @@ -0,0 +1,66 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +/** + * @author idobre + * @since 15/11/2017 + */ +public class AbstractExcelSheetTest { + private class MockExcelSheet extends AbstractExcelSheet { + public MockExcelSheet(Workbook workbook) { + super(workbook); + } + + @Override + public void writeRow(Class clazz, Object object, Row row) { + + } + + @Override + public void writeSheet(Class clazz, List objects) { + + } + + @Override + public int writeSheetGetLink(Class clazz, List objects) { + return 0; + } + + @Override + public String getExcelSheetName() { + return null; + } + + @Override + public void emptySheet() { + + } + } + + @Test + public void writeCell() throws Exception { + final Workbook workbook = new XSSFWorkbook(); + final ExcelSheet excelSheet = new MockExcelSheet(workbook); + final Sheet sheet = workbook.createSheet("sheet"); + final Row row = sheet.createRow(0); + + excelSheet.writeCell(null, row, 0); + excelSheet.writeCell(Boolean.TRUE, row, 1); + excelSheet.writeCell("text", row, 2); + excelSheet.writeCell(1, row, 3); + + Assert.assertEquals(CellType.BLANK, row.getCell(0).getCellTypeEnum()); + Assert.assertEquals("Yes", row.getCell(1).getStringCellValue()); + Assert.assertEquals(CellType.STRING, row.getCell(2).getCellTypeEnum()); + Assert.assertEquals(CellType.NUMERIC, row.getCell(3).getCellTypeEnum()); + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFieldServiceTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFieldServiceTest.java new file mode 100644 index 000000000..1cd4c5232 --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFieldServiceTest.java @@ -0,0 +1,121 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; +import org.devgateway.toolkit.persistence.excel.info.ClassFields; +import org.devgateway.toolkit.persistence.excel.info.ClassFieldsDefault; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.data.domain.Persistable; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author idobre + * @since 10/11/2017 + */ +public class ExcelFieldServiceTest { + private class TestClass implements Persistable { + private static final long serialVersionUID = 1L; + + private Long id; + + private List label; + + @ExcelExport(justExport = true) + private OtherClass otherClass1; + + @ExcelExport(separateSheet = true) + private OtherClass otherClass2; + + public TestClass(final Long id) { + this.id = id; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + } + + private class OtherClass { + private Boolean valid; + + @ExcelExport(name = "money") + private Integer amount; + } + + @Test + public void getFieldType() throws Exception { + final ClassFields classFields = new ClassFieldsDefault(TestClass.class); + final Iterator fields = classFields.getFields(); + + final Field firstField = fields.next(); // get first element + Assert.assertEquals("Check basic field type", FieldType.basic, ExcelFieldService.getFieldType(firstField)); + + final Field secondField = fields.next(); // get second element + Assert.assertEquals("Check type class for a List", FieldType.basic, ExcelFieldService.getFieldType(secondField)); + + final Field thirdField = fields.next(); // get third element + Assert.assertEquals("Check basic field type for Object", FieldType.basic, ExcelFieldService.getFieldType(thirdField)); + + final Field fourthField = fields.next(); // get fourth element + Assert.assertEquals("Check objectSeparateSheet field type Object", FieldType.objectSeparateSheet, ExcelFieldService.getFieldType(fourthField)); + } + + @Test + public void getFieldClass() throws Exception { + final ClassFields classFields = new ClassFieldsDefault(TestClass.class); + final Iterator fields = classFields.getFields(); + + final Field firstField = fields.next(); // get first element + Assert.assertEquals("Check basic field class", Long.class, ExcelFieldService.getFieldClass(firstField)); + + + final Field secondField = fields.next(); // get second element + Assert.assertEquals("Check field class for a List", String.class, ExcelFieldService.getFieldClass(secondField)); + } + + @Test + public void getFields() throws Exception { + final String[] expectedFields = {"otherClass1", "otherClass2"}; + + final Iterator fields = ExcelFieldService.getFields(TestClass.class); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check get fields", expectedFields, actualFields.toArray()); + } + + @Test + public void getObjectID() throws Exception { + final OtherClass otherClass = new OtherClass(); + Assert.assertEquals("Check object ID", Long.valueOf(-1), ExcelFieldService.getObjectID(otherClass)); + + final TestClass testclass = new TestClass((long) 10); + Assert.assertEquals("Check object ID", Long.valueOf(10), ExcelFieldService.getObjectID(testclass)); + } + + @Test + public void getFieldName() throws Exception { + final ClassFields classFields = new ClassFieldsDefault(OtherClass.class); + final Iterator fields = classFields.getFields(); + + final Field firstField = fields.next(); // get first element + Assert.assertEquals("Check field name", "valid", ExcelFieldService.getFieldName(firstField)); + + final Field secondField = fields.next(); // get second element + Assert.assertEquals("Check field name", "money", ExcelFieldService.getFieldName(secondField)); + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefaultTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefaultTest.java new file mode 100644 index 000000000..66f05c3b0 --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefaultTest.java @@ -0,0 +1,345 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.log4j.Logger; +import org.apache.poi.ss.usermodel.Workbook; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.data.domain.Persistable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author idobre + * @since 13/11/2017 + */ +public class ExcelFileDefaultTest { + private static final Logger logger = Logger.getLogger(ExcelFileDefaultTest.class); + + @Test + public void createWorkbook() throws Exception { + final ExcelFile excelFile = new ExcelFileDefault(createObjects()); + final Workbook workbook = excelFile.createWorkbook(); + + // try (FileOutputStream outputStream = new FileOutputStream("/Users/ionut/Downloads/file-export.xlsx")) { + // workbook.write(outputStream); + // } + + Assert.assertEquals("Number of sheets", 3, workbook.getNumberOfSheets()); + + Assert.assertNotNull("buyer sheet", workbook.getSheet("testbuyer")); + Assert.assertNotNull("contract sheet", workbook.getSheet("testcontract")); + Assert.assertNotNull("document sheet", workbook.getSheet("testdocument")); + + Assert.assertEquals("buyer name", "buyer 1", + workbook.getSheet("testbuyer").getRow(1).getCell(0).toString()); + Assert.assertEquals("buyer classification", "TP-1 - alii aliquam", + workbook.getSheet("testbuyer").getRow(2).getCell(8).toString()); + + Assert.assertEquals("contract parent", "testbuyer - 1", + workbook.getSheet("testcontract").getRow(1).getCell(0).toString()); + Assert.assertEquals("contract amount", 1000, + workbook.getSheet("testcontract").getRow(1).getCell(2).getNumericCellValue(), 0.0); + + + Assert.assertEquals("document number of rows", 2, workbook.getSheet("testdocument").getLastRowNum()); + + Assert.assertEquals("buyer address flatten", "Street 1 | Street 2", + workbook.getSheet("testbuyer").getRow(1).getCell(4).toString()); + } + + private List createObjects() { + final List objects = new ArrayList(); + + final List addresses1 = new ArrayList<>(); + final List addresses2 = new ArrayList<>(); + final TestAddress address1 = new TestAddress(Long.valueOf(1), "Street 1", "Romania"); + final TestAddress address2 = new TestAddress(Long.valueOf(2), "Street 2", "France"); + final TestAddress address3 = new TestAddress(Long.valueOf(1), "Street 3", "UK"); + final TestAddress address4 = new TestAddress(Long.valueOf(2), "Street 4", "Moldova"); + addresses1.add(address1); + addresses1.add(address2); + addresses2.add(address3); + addresses2.add(address4); + + final TestOrganization organization1 = new TestOrganization(Long.valueOf(1), "organization 1", addresses1); + final TestOrganization organization2 = new TestOrganization(Long.valueOf(2), "organization 2", addresses1); + + final List contracts1 = new ArrayList<>(); + final List contracts2 = new ArrayList<>(); + final TestContract contract1 = new TestContract(Long.valueOf(1), "ABC-100", 1000); + final TestContract contract2 = new TestContract(Long.valueOf(2), "ABC-101", 2000); + final TestContract contract3 = new TestContract(Long.valueOf(3), "ABC-102", 100); + final TestContract contract4 = new TestContract(Long.valueOf(4), "ABC-103", 7000); + contracts1.add(contract1); + contracts1.add(contract2); + contracts2.add(contract3); + contracts2.add(contract4); + + final TestDocument document1 = new TestDocument(Long.valueOf(1), "document 1", "PDF"); + final TestDocument document2 = new TestDocument(Long.valueOf(2), "document 2", "TXT"); + + final TestClassification classification1 = new TestClassification(Long.valueOf(1), "XZ-1", "dolor sit amet"); + final TestClassification classification2 = new TestClassification(Long.valueOf(2), "TP-1", "alii aliquam"); + + final TestBuyer buyer1 = new TestBuyer(Long.valueOf(1), "buyer 1", organization1, + addresses1, contracts1, document1, classification1); + final TestBuyer buyer2 = new TestBuyer(Long.valueOf(2), "buyer 2", organization2, + addresses2, contracts2, document2, classification2); + + objects.add(buyer1); + objects.add(buyer2); + + return objects; + } + + + /** ****************************************************************************** + * Test Entities + ******************************************************************************* */ + public class TestBuyer implements Persistable { + private final Long id; + + @ExcelExport + private final String name; + + @ExcelExport(name = "organization") + private final TestOrganization org; + + @ExcelExport + private final List addresses; + + @ExcelExport(separateSheet = true) + private final List contracts; + + @ExcelExport(separateSheet = true) + private final TestDocument document; + + @ExcelExport(justExport = true) + private final TestClassification classification; + + private TestBuyer(final Long id, final String name, final TestOrganization org, + final List addresses, final List contracts, + final TestDocument document, final TestClassification classification) { + this.id = id; + this.name = name; + this.org = org; + this.addresses = addresses; + this.contracts = contracts; + this.document = document; + this.classification = classification; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public String getName() { + return name; + } + + public TestOrganization getOrg() { + return org; + } + + public List getAddresses() { + return addresses; + } + + public List getContracts() { + return contracts; + } + + public TestDocument getDocument() { + return document; + } + + public TestClassification getClassification() { + return classification; + } + } + + public class TestOrganization implements Persistable { + private final Long id; + + @ExcelExport + private final List addresses; + + @ExcelExport + private final String name; + + private TestOrganization(final Long id, final String name, final List addresses) { + this.id = id; + this.name = name; + this.addresses = addresses; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public List getAddresses() { + return addresses; + } + + public String getName() { + return name; + } + } + + public class TestAddress implements Persistable { + private final Long id; + + @ExcelExport + private final String street; + + @ExcelExport + private final String country; + + public TestAddress(final Long id, final String street, final String country) { + this.id = id; + this.street = street; + this.country = country; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public String getStreet() { + return street; + } + + public String getCountry() { + return country; + } + } + + + public class TestContract implements Persistable { + private final Long id; + + @ExcelExport + private final String identifier; + + @ExcelExport + private final int amount; + + public TestContract(final Long id, final String identifier, final int amount) { + this.id = id; + this.identifier = identifier; + this.amount = amount; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public String getIdentifier() { + return identifier; + } + + public int getAmount() { + return amount; + } + } + + public class TestDocument implements Persistable { + private final Long id; + + @ExcelExport + private final String fileName; + + @ExcelExport + private final String type; + + public TestDocument(final Long id, final String fileName, final String type) { + this.id = id; + this.fileName = fileName; + this.type = type; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public String getFileName() { + return fileName; + } + + public String getType() { + return type; + } + } + + public class TestClassification implements Persistable { + private final Long id; + + @ExcelExport + private final String schema; + + @ExcelExport + private final String description; + + public TestClassification(final Long id, final String schema, final String description) { + this.id = id; + this.schema = schema; + this.description = description; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + + @Override + public String toString() { + return schema + " - " + description; + } + + public String getSchema() { + return schema; + } + + public String getDescription() { + return description; + } + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefaultTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefaultTest.java new file mode 100644 index 000000000..6c39ef7c1 --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefaultTest.java @@ -0,0 +1,61 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author idobre + * @since 10/11/2017 + */ +public class ClassFieldsDefaultTest { + private class TestClass { + private static final long serialVersionUID = 1L; + + private int id; + + private String label; + } + + private class TestClassImproved extends TestClass { + private static final long serialVersionUID = 1L; + + private Boolean valid; + } + + @Test + public void getFields() throws Exception { + final String[] expectedFields = {"id", "label"}; + + final ClassFields classFields = new ClassFieldsDefault(TestClass.class); + final Iterator fields = classFields.getFields(); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check declared fields", expectedFields, actualFields.toArray()); + } + + @Test + public void getInheritedFields() throws Exception { + final String[] expectedFields = {"id", "label", "valid"}; + + final ClassFields classFields = new ClassFieldsDefault(TestClassImproved.class, true); + final Iterator fields = classFields.getFields(); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check declared & inherited fields", expectedFields, actualFields.toArray()); + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExportTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExportTest.java new file mode 100644 index 000000000..e7782005d --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExportTest.java @@ -0,0 +1,75 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author idobre + * @since 10/11/2017 + */ +public class ClassFieldsExcelExportTest { + private class TestClass { + private static final long serialVersionUID = 1L; + + @ExcelExport(name = "id") + private int id; + + @ExcelExport + private String label; + + private String description; + } + + private class TestClassImproved extends TestClass { + private static final long serialVersionUID = 1L; + + @ExcelExport + private Boolean valid; + + private Long amount; + } + + + @Test + public void getFields() throws Exception { + final String[] expectedFields = {"id", "label"}; + + final ClassFields classFields = new ClassFieldsExcelExport( + new ClassFieldsDefault(TestClass.class) + ); + final Iterator fields = classFields.getFields(); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check declared @ExcelExport fields", expectedFields, actualFields.toArray()); + } + + @Test + public void getInheritedFields() throws Exception { + final String[] expectedFields = {"id", "label", "valid"}; + + final ClassFields classFields = new ClassFieldsExcelExport( + new ClassFieldsDefault(TestClassImproved.class, true) + ); + final Iterator fields = classFields.getFields(); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check declared & inherited @ExcelExport fields", + expectedFields, actualFields.toArray()); + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/test/AbstractPersistenceTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/test/AbstractPersistenceTest.java index 0748544cc..2eb01f15a 100644 --- a/persistence/src/test/java/org/devgateway/toolkit/persistence/test/AbstractPersistenceTest.java +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/test/AbstractPersistenceTest.java @@ -13,7 +13,7 @@ */ @RunWith(SpringRunner.class) @ActiveProfiles("integration") -@SpringBootTest(classes = { PersistenceApplication.class }) +@SpringBootTest(classes = { PersistenceApplication.class }, properties = {"net.sf.ehcache.disabled=true"}) @TestPropertySource("classpath:test.properties") public abstract class AbstractPersistenceTest { diff --git a/pom.xml b/pom.xml index e25995056..a6e98617b 100644 --- a/pom.xml +++ b/pom.xml @@ -22,10 +22,10 @@ UTF-8 1.8 3.5.3 - 1.5.3.RELEASE - 10.13.1.1 - 3.14 - 3.14 + 1.5.10.RELEASE + 10.14.1.0 + 3.17 + 3.17 3.5 devgateway/toolkit 0.4.13 diff --git a/ui/.babelrc b/ui/.babelrc index facd18092..efcfa319d 100644 --- a/ui/.babelrc +++ b/ui/.babelrc @@ -1,3 +1,4 @@ { - "presets": ["es2015", "react"] + "presets": ["es2015", "react", "flow"], + "plugins": ["transform-object-rest-spread"] } \ No newline at end of file diff --git a/ui/.eslintrc b/ui/.eslintrc index 466fb61f1..268c69c2e 100644 --- a/ui/.eslintrc +++ b/ui/.eslintrc @@ -1,13 +1,19 @@ { - "extends": "airbnb", - "env": { - "browser": true - }, - "rules": { - "linebreak-style": 0, - "import/no-extraneous-dependencies": [error, { devDependencies: true }] - }, - "settings": { - "import/resolver": "webpack" - } + "extends": "airbnb", + "env": { + "browser": true + }, + "rules": { + "linebreak-style": 0, + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], + "import/extensions": 0, + "react/react-in-jsx-scope": 0, + "react/no-find-dom-node": 0, + "no-underscore-dangle": 0, + "jsx-a11y/href-no-hash": "off", + "jsx-a11y/anchor-is-valid": ["warn", { "aspects": ["invalidHref"] }] + }, + "settings": { + "import/resolver": "webpack" + } } diff --git a/ui/.flowconfig b/ui/.flowconfig new file mode 100644 index 000000000..6ed79266b --- /dev/null +++ b/ui/.flowconfig @@ -0,0 +1,18 @@ +[ignore] +.*/assets +.*/dll +.*/node +.*/node_modules +.*/public +.*/src +.*/target + +[include] + +[libs] + +[lints] + +[options] + +[strict] diff --git a/ui/assets/icons/COLLUSION.png b/ui/assets/dg-logo-small.png similarity index 74% rename from ui/assets/icons/COLLUSION.png rename to ui/assets/dg-logo-small.png index 1b959ed1c..dbda029cd 100644 Binary files a/ui/assets/icons/COLLUSION.png and b/ui/assets/dg-logo-small.png differ diff --git a/ui/assets/flags/es_ES.png b/ui/assets/flags/es_ES.png new file mode 100644 index 000000000..6daa2dc25 Binary files /dev/null and b/ui/assets/flags/es_ES.png differ diff --git a/ui/assets/flags/fr_FR.png b/ui/assets/flags/fr_FR.png new file mode 100644 index 000000000..25cf726d1 Binary files /dev/null and b/ui/assets/flags/fr_FR.png differ diff --git a/ui/assets/icons/FRAUD.png b/ui/assets/icons/FRAUD.png deleted file mode 100644 index 1239f1133..000000000 Binary files a/ui/assets/icons/FRAUD.png and /dev/null differ diff --git a/ui/assets/icons/RIGGING.png b/ui/assets/icons/RIGGING.png deleted file mode 100644 index c3f7f6faf..000000000 Binary files a/ui/assets/icons/RIGGING.png and /dev/null differ diff --git a/ui/assets/icons/blue/COLLUSION.svg b/ui/assets/icons/blue/COLLUSION.svg new file mode 100644 index 000000000..59ec94d97 --- /dev/null +++ b/ui/assets/icons/blue/COLLUSION.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/ui/assets/icons/blue/FRAUD.svg b/ui/assets/icons/blue/FRAUD.svg new file mode 100644 index 000000000..8ebd9002e --- /dev/null +++ b/ui/assets/icons/blue/FRAUD.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/ui/assets/icons/blue/RIGGING.svg b/ui/assets/icons/blue/RIGGING.svg new file mode 100644 index 000000000..dccc4cd40 --- /dev/null +++ b/ui/assets/icons/blue/RIGGING.svg @@ -0,0 +1,21 @@ + + + + + + + diff --git a/ui/assets/icons/blue/contracts.svg b/ui/assets/icons/blue/contracts.svg new file mode 100644 index 000000000..6e323714c --- /dev/null +++ b/ui/assets/icons/blue/contracts.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/ui/assets/icons/blue/overview.svg b/ui/assets/icons/blue/overview.svg new file mode 100644 index 000000000..7b18b4996 --- /dev/null +++ b/ui/assets/icons/blue/overview.svg @@ -0,0 +1,8 @@ + + + + diff --git a/ui/assets/icons/blue/procuring-entities.svg b/ui/assets/icons/blue/procuring-entities.svg new file mode 100644 index 000000000..ef5096987 --- /dev/null +++ b/ui/assets/icons/blue/procuring-entities.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/ui/assets/icons/blue/suppliers.svg b/ui/assets/icons/blue/suppliers.svg new file mode 100644 index 000000000..49438aae6 --- /dev/null +++ b/ui/assets/icons/blue/suppliers.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/ui/assets/icons/export-very-black.svg b/ui/assets/icons/export-very-black.svg new file mode 100644 index 000000000..59417e99a --- /dev/null +++ b/ui/assets/icons/export-very-black.svg @@ -0,0 +1,7 @@ + + + + diff --git a/ui/assets/icons/flag.svg b/ui/assets/icons/flag.svg new file mode 100644 index 000000000..539fa292f --- /dev/null +++ b/ui/assets/icons/flag.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/ui/assets/icons/white/COLLUSION.svg b/ui/assets/icons/white/COLLUSION.svg new file mode 100644 index 000000000..3ec71ac95 --- /dev/null +++ b/ui/assets/icons/white/COLLUSION.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/ui/assets/icons/white/FRAUD.svg b/ui/assets/icons/white/FRAUD.svg new file mode 100644 index 000000000..a770cefe8 --- /dev/null +++ b/ui/assets/icons/white/FRAUD.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/ui/assets/icons/white/RIGGING.svg b/ui/assets/icons/white/RIGGING.svg new file mode 100644 index 000000000..9b869512a --- /dev/null +++ b/ui/assets/icons/white/RIGGING.svg @@ -0,0 +1,21 @@ + + + + + + + diff --git a/ui/assets/icons/white/contracts.svg b/ui/assets/icons/white/contracts.svg new file mode 100644 index 000000000..cd9277bc9 --- /dev/null +++ b/ui/assets/icons/white/contracts.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/ui/assets/icons/white/overview.svg b/ui/assets/icons/white/overview.svg new file mode 100644 index 000000000..a6ee1b391 --- /dev/null +++ b/ui/assets/icons/white/overview.svg @@ -0,0 +1,8 @@ + + + + diff --git a/ui/assets/icons/white/procuring-entities.svg b/ui/assets/icons/white/procuring-entities.svg new file mode 100644 index 000000000..875b0510d --- /dev/null +++ b/ui/assets/icons/white/procuring-entities.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/ui/assets/icons/white/suppliers.svg b/ui/assets/icons/white/suppliers.svg new file mode 100644 index 000000000..a60eea556 --- /dev/null +++ b/ui/assets/icons/white/suppliers.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/ui/dev.html b/ui/dev.html index 33aff051f..d4348852f 100644 --- a/ui/dev.html +++ b/ui/dev.html @@ -1,12 +1,14 @@ - - - OCVN - - - -
- - - \ No newline at end of file + + + OC Explorer + + + + +
+ + + + diff --git a/ui/dll/manifest.json b/ui/dll/manifest.json new file mode 100644 index 000000000..9278c9012 --- /dev/null +++ b/ui/dll/manifest.json @@ -0,0 +1,928 @@ +{ + "name": "lib", + "content": { + "./node_modules/react/react.js": 1, + "./node_modules/react/lib/React.js": 2, + "./node_modules/process/browser.js": 3, + "./node_modules/object-assign/index.js": 4, + "./node_modules/react/lib/ReactBaseClasses.js": 5, + "./node_modules/react/lib/reactProdInvariant.js": 6, + "./node_modules/react/lib/ReactNoopUpdateQueue.js": 7, + "./node_modules/fbjs/lib/warning.js": 8, + "./node_modules/fbjs/lib/emptyFunction.js": 9, + "./node_modules/react/lib/canDefineProperty.js": 10, + "./node_modules/fbjs/lib/emptyObject.js": 11, + "./node_modules/fbjs/lib/invariant.js": 12, + "./node_modules/react/lib/lowPriorityWarning.js": 13, + "./node_modules/react/lib/ReactChildren.js": 14, + "./node_modules/react/lib/PooledClass.js": 15, + "./node_modules/react/lib/ReactElement.js": 16, + "./node_modules/react/lib/ReactCurrentOwner.js": 17, + "./node_modules/react/lib/ReactElementSymbol.js": 18, + "./node_modules/react/lib/traverseAllChildren.js": 19, + "./node_modules/react/lib/getIteratorFn.js": 20, + "./node_modules/react/lib/KeyEscapeUtils.js": 21, + "./node_modules/react/lib/ReactDOMFactories.js": 22, + "./node_modules/react/lib/ReactElementValidator.js": 23, + "./node_modules/react/lib/ReactComponentTreeHook.js": 24, + "./node_modules/react/lib/checkReactTypeSpec.js": 25, + "./node_modules/react/lib/ReactPropTypeLocationNames.js": 26, + "./node_modules/react/lib/ReactPropTypesSecret.js": 27, + "./node_modules/react/lib/ReactPropTypes.js": 28, + "./node_modules/prop-types/factory.js": 29, + "./node_modules/prop-types/factoryWithTypeCheckers.js": 30, + "./node_modules/prop-types/node_modules/fbjs/lib/emptyFunction.js": 31, + "./node_modules/prop-types/node_modules/fbjs/lib/invariant.js": 32, + "./node_modules/prop-types/node_modules/fbjs/lib/warning.js": 33, + "./node_modules/prop-types/lib/ReactPropTypesSecret.js": 34, + "./node_modules/prop-types/checkPropTypes.js": 35, + "./node_modules/react/lib/ReactVersion.js": 36, + "./node_modules/react/lib/createClass.js": 37, + "./node_modules/react/node_modules/create-react-class/factory.js": 38, + "./node_modules/react/node_modules/create-react-class/node_modules/object-assign/index.js": 39, + "./node_modules/react/lib/onlyChild.js": 40, + "./node_modules/react-dom/index.js": 41, + "./node_modules/react-dom/lib/ReactDOM.js": 42, + "./node_modules/react-dom/lib/ReactDOMComponentTree.js": 43, + "./node_modules/react-dom/lib/reactProdInvariant.js": 44, + "./node_modules/react-dom/lib/DOMProperty.js": 45, + "./node_modules/react-dom/lib/ReactDOMComponentFlags.js": 46, + "./node_modules/react-dom/lib/ReactDefaultInjection.js": 47, + "./node_modules/react-dom/lib/ARIADOMPropertyConfig.js": 48, + "./node_modules/react-dom/lib/BeforeInputEventPlugin.js": 49, + "./node_modules/react-dom/lib/EventPropagators.js": 50, + "./node_modules/react-dom/lib/EventPluginHub.js": 51, + "./node_modules/react-dom/lib/EventPluginRegistry.js": 52, + "./node_modules/react-dom/lib/EventPluginUtils.js": 53, + "./node_modules/react-dom/lib/ReactErrorUtils.js": 54, + "./node_modules/react-dom/lib/accumulateInto.js": 55, + "./node_modules/react-dom/lib/forEachAccumulated.js": 56, + "./node_modules/fbjs/lib/ExecutionEnvironment.js": 57, + "./node_modules/react-dom/lib/FallbackCompositionState.js": 58, + "./node_modules/react-dom/lib/PooledClass.js": 59, + "./node_modules/react-dom/lib/getTextContentAccessor.js": 60, + "./node_modules/react-dom/lib/SyntheticCompositionEvent.js": 61, + "./node_modules/react-dom/lib/SyntheticEvent.js": 62, + "./node_modules/react-dom/lib/SyntheticInputEvent.js": 63, + "./node_modules/react-dom/lib/ChangeEventPlugin.js": 64, + "./node_modules/react-dom/lib/ReactUpdates.js": 65, + "./node_modules/react-dom/lib/CallbackQueue.js": 66, + "./node_modules/react-dom/lib/ReactFeatureFlags.js": 67, + "./node_modules/react-dom/lib/ReactReconciler.js": 68, + "./node_modules/react-dom/lib/ReactRef.js": 69, + "./node_modules/react-dom/lib/ReactOwner.js": 70, + "./node_modules/react-dom/lib/ReactInstrumentation.js": 71, + "./node_modules/react-dom/lib/ReactDebugTool.js": 72, + "./node_modules/react-dom/lib/ReactInvalidSetStateWarningHook.js": 73, + "./node_modules/react-dom/lib/ReactHostOperationHistoryHook.js": 74, + "./node_modules/fbjs/lib/performanceNow.js": 75, + "./node_modules/fbjs/lib/performance.js": 76, + "./node_modules/react-dom/lib/Transaction.js": 77, + "./node_modules/react-dom/lib/inputValueTracking.js": 78, + "./node_modules/react-dom/lib/getEventTarget.js": 79, + "./node_modules/react-dom/lib/isEventSupported.js": 80, + "./node_modules/react-dom/lib/isTextInputElement.js": 81, + "./node_modules/react-dom/lib/DefaultEventPluginOrder.js": 82, + "./node_modules/react-dom/lib/EnterLeaveEventPlugin.js": 83, + "./node_modules/react-dom/lib/SyntheticMouseEvent.js": 84, + "./node_modules/react-dom/lib/SyntheticUIEvent.js": 85, + "./node_modules/react-dom/lib/ViewportMetrics.js": 86, + "./node_modules/react-dom/lib/getEventModifierState.js": 87, + "./node_modules/react-dom/lib/HTMLDOMPropertyConfig.js": 88, + "./node_modules/react-dom/lib/ReactComponentBrowserEnvironment.js": 89, + "./node_modules/react-dom/lib/DOMChildrenOperations.js": 90, + "./node_modules/react-dom/lib/DOMLazyTree.js": 91, + "./node_modules/react-dom/lib/DOMNamespaces.js": 92, + "./node_modules/react-dom/lib/setInnerHTML.js": 93, + "./node_modules/react-dom/lib/createMicrosoftUnsafeLocalFunction.js": 94, + "./node_modules/react-dom/lib/setTextContent.js": 95, + "./node_modules/react-dom/lib/escapeTextContentForBrowser.js": 96, + "./node_modules/react-dom/lib/Danger.js": 97, + "./node_modules/fbjs/lib/createNodesFromMarkup.js": 98, + "./node_modules/fbjs/lib/createArrayFromMixed.js": 99, + "./node_modules/fbjs/lib/getMarkupWrap.js": 100, + "./node_modules/react-dom/lib/ReactDOMIDOperations.js": 101, + "./node_modules/react-dom/lib/ReactDOMComponent.js": 102, + "./node_modules/react-dom/lib/AutoFocusUtils.js": 103, + "./node_modules/fbjs/lib/focusNode.js": 104, + "./node_modules/react-dom/lib/CSSPropertyOperations.js": 105, + "./node_modules/react-dom/lib/CSSProperty.js": 106, + "./node_modules/fbjs/lib/camelizeStyleName.js": 107, + "./node_modules/fbjs/lib/camelize.js": 108, + "./node_modules/react-dom/lib/dangerousStyleValue.js": 109, + "./node_modules/fbjs/lib/hyphenateStyleName.js": 110, + "./node_modules/fbjs/lib/hyphenate.js": 111, + "./node_modules/fbjs/lib/memoizeStringOnly.js": 112, + "./node_modules/react-dom/lib/DOMPropertyOperations.js": 113, + "./node_modules/react-dom/lib/quoteAttributeValueForBrowser.js": 114, + "./node_modules/react-dom/lib/ReactBrowserEventEmitter.js": 115, + "./node_modules/react-dom/lib/ReactEventEmitterMixin.js": 116, + "./node_modules/react-dom/lib/getVendorPrefixedEventName.js": 117, + "./node_modules/react-dom/lib/ReactDOMInput.js": 118, + "./node_modules/react-dom/lib/LinkedValueUtils.js": 119, + "./node_modules/react-dom/lib/ReactPropTypesSecret.js": 120, + "./node_modules/react-dom/lib/ReactDOMOption.js": 121, + "./node_modules/react-dom/lib/ReactDOMSelect.js": 122, + "./node_modules/react-dom/lib/ReactDOMTextarea.js": 123, + "./node_modules/react-dom/lib/ReactMultiChild.js": 124, + "./node_modules/react-dom/lib/ReactComponentEnvironment.js": 125, + "./node_modules/react-dom/lib/ReactInstanceMap.js": 126, + "./node_modules/react-dom/lib/ReactChildReconciler.js": 127, + "./node_modules/react-dom/lib/instantiateReactComponent.js": 128, + "./node_modules/react-dom/lib/ReactCompositeComponent.js": 129, + "./node_modules/react-dom/lib/ReactNodeTypes.js": 130, + "./node_modules/react-dom/lib/checkReactTypeSpec.js": 131, + "./node_modules/react-dom/lib/ReactPropTypeLocationNames.js": 132, + "./node_modules/fbjs/lib/shallowEqual.js": 133, + "./node_modules/react-dom/lib/shouldUpdateReactComponent.js": 134, + "./node_modules/react-dom/lib/ReactEmptyComponent.js": 135, + "./node_modules/react-dom/lib/ReactHostComponent.js": 136, + "./node_modules/react/lib/getNextDebugID.js": 137, + "./node_modules/react-dom/lib/KeyEscapeUtils.js": 138, + "./node_modules/react-dom/lib/traverseAllChildren.js": 139, + "./node_modules/react-dom/lib/ReactElementSymbol.js": 140, + "./node_modules/react-dom/lib/getIteratorFn.js": 141, + "./node_modules/react-dom/lib/flattenChildren.js": 142, + "./node_modules/react-dom/lib/ReactServerRenderingTransaction.js": 143, + "./node_modules/react-dom/lib/ReactServerUpdateQueue.js": 144, + "./node_modules/react-dom/lib/ReactUpdateQueue.js": 145, + "./node_modules/react-dom/lib/validateDOMNesting.js": 146, + "./node_modules/react-dom/lib/ReactDOMEmptyComponent.js": 147, + "./node_modules/react-dom/lib/ReactDOMTreeTraversal.js": 148, + "./node_modules/react-dom/lib/ReactDOMTextComponent.js": 149, + "./node_modules/react-dom/lib/ReactDefaultBatchingStrategy.js": 150, + "./node_modules/react-dom/lib/ReactEventListener.js": 151, + "./node_modules/fbjs/lib/EventListener.js": 152, + "./node_modules/fbjs/lib/getUnboundedScrollPosition.js": 153, + "./node_modules/react-dom/lib/ReactInjection.js": 154, + "./node_modules/react-dom/lib/ReactReconcileTransaction.js": 155, + "./node_modules/react-dom/lib/ReactInputSelection.js": 156, + "./node_modules/react-dom/lib/ReactDOMSelection.js": 157, + "./node_modules/react-dom/lib/getNodeForCharacterOffset.js": 158, + "./node_modules/fbjs/lib/containsNode.js": 159, + "./node_modules/fbjs/lib/isTextNode.js": 160, + "./node_modules/fbjs/lib/isNode.js": 161, + "./node_modules/fbjs/lib/getActiveElement.js": 162, + "./node_modules/react-dom/lib/SVGDOMPropertyConfig.js": 163, + "./node_modules/react-dom/lib/SelectEventPlugin.js": 164, + "./node_modules/react-dom/lib/SimpleEventPlugin.js": 165, + "./node_modules/react-dom/lib/SyntheticAnimationEvent.js": 166, + "./node_modules/react-dom/lib/SyntheticClipboardEvent.js": 167, + "./node_modules/react-dom/lib/SyntheticFocusEvent.js": 168, + "./node_modules/react-dom/lib/SyntheticKeyboardEvent.js": 169, + "./node_modules/react-dom/lib/getEventCharCode.js": 170, + "./node_modules/react-dom/lib/getEventKey.js": 171, + "./node_modules/react-dom/lib/SyntheticDragEvent.js": 172, + "./node_modules/react-dom/lib/SyntheticTouchEvent.js": 173, + "./node_modules/react-dom/lib/SyntheticTransitionEvent.js": 174, + "./node_modules/react-dom/lib/SyntheticWheelEvent.js": 175, + "./node_modules/react-dom/lib/ReactMount.js": 176, + "./node_modules/react-dom/lib/ReactDOMContainerInfo.js": 177, + "./node_modules/react-dom/lib/ReactDOMFeatureFlags.js": 178, + "./node_modules/react-dom/lib/ReactMarkupChecksum.js": 179, + "./node_modules/react-dom/lib/adler32.js": 180, + "./node_modules/react-dom/lib/ReactVersion.js": 181, + "./node_modules/react-dom/lib/findDOMNode.js": 182, + "./node_modules/react-dom/lib/getHostComponentFromComposite.js": 183, + "./node_modules/react-dom/lib/renderSubtreeIntoContainer.js": 184, + "./node_modules/react-dom/lib/ReactDOMUnknownPropertyHook.js": 185, + "./node_modules/react-dom/lib/ReactDOMNullInputValuePropHook.js": 186, + "./node_modules/react-dom/lib/ReactDOMInvalidARIAHook.js": 187, + "./node_modules/react-bootstrap/lib/index.js": 188, + "./node_modules/react-bootstrap/lib/Accordion.js": 189, + "./node_modules/babel-runtime/helpers/extends.js": 190, + "./node_modules/babel-runtime/core-js/object/assign.js": 191, + "./node_modules/core-js/library/fn/object/assign.js": 192, + "./node_modules/core-js/library/modules/es6.object.assign.js": 193, + "./node_modules/core-js/library/modules/_export.js": 194, + "./node_modules/core-js/library/modules/_global.js": 195, + "./node_modules/core-js/library/modules/_core.js": 196, + "./node_modules/core-js/library/modules/_ctx.js": 197, + "./node_modules/core-js/library/modules/_a-function.js": 198, + "./node_modules/core-js/library/modules/_hide.js": 199, + "./node_modules/core-js/library/modules/_object-dp.js": 200, + "./node_modules/core-js/library/modules/_an-object.js": 201, + "./node_modules/core-js/library/modules/_is-object.js": 202, + "./node_modules/core-js/library/modules/_ie8-dom-define.js": 203, + "./node_modules/core-js/library/modules/_descriptors.js": 204, + "./node_modules/core-js/library/modules/_fails.js": 205, + "./node_modules/core-js/library/modules/_dom-create.js": 206, + "./node_modules/core-js/library/modules/_to-primitive.js": 207, + "./node_modules/core-js/library/modules/_property-desc.js": 208, + "./node_modules/core-js/library/modules/_object-assign.js": 209, + "./node_modules/core-js/library/modules/_object-keys.js": 210, + "./node_modules/core-js/library/modules/_object-keys-internal.js": 211, + "./node_modules/core-js/library/modules/_has.js": 212, + "./node_modules/core-js/library/modules/_to-iobject.js": 213, + "./node_modules/core-js/library/modules/_iobject.js": 214, + "./node_modules/core-js/library/modules/_cof.js": 215, + "./node_modules/core-js/library/modules/_defined.js": 216, + "./node_modules/core-js/library/modules/_array-includes.js": 217, + "./node_modules/core-js/library/modules/_to-length.js": 218, + "./node_modules/core-js/library/modules/_to-integer.js": 219, + "./node_modules/core-js/library/modules/_to-index.js": 220, + "./node_modules/core-js/library/modules/_shared-key.js": 221, + "./node_modules/core-js/library/modules/_shared.js": 222, + "./node_modules/core-js/library/modules/_uid.js": 223, + "./node_modules/core-js/library/modules/_enum-bug-keys.js": 224, + "./node_modules/core-js/library/modules/_object-gops.js": 225, + "./node_modules/core-js/library/modules/_object-pie.js": 226, + "./node_modules/core-js/library/modules/_to-object.js": 227, + "./node_modules/babel-runtime/helpers/classCallCheck.js": 228, + "./node_modules/babel-runtime/helpers/possibleConstructorReturn.js": 229, + "./node_modules/babel-runtime/helpers/typeof.js": 230, + "./node_modules/babel-runtime/core-js/symbol/iterator.js": 231, + "./node_modules/core-js/library/fn/symbol/iterator.js": 232, + "./node_modules/core-js/library/modules/es6.string.iterator.js": 233, + "./node_modules/core-js/library/modules/_string-at.js": 234, + "./node_modules/core-js/library/modules/_iter-define.js": 235, + "./node_modules/core-js/library/modules/_library.js": 236, + "./node_modules/core-js/library/modules/_redefine.js": 237, + "./node_modules/core-js/library/modules/_iterators.js": 238, + "./node_modules/core-js/library/modules/_iter-create.js": 239, + "./node_modules/core-js/library/modules/_object-create.js": 240, + "./node_modules/core-js/library/modules/_object-dps.js": 241, + "./node_modules/core-js/library/modules/_html.js": 242, + "./node_modules/core-js/library/modules/_set-to-string-tag.js": 243, + "./node_modules/core-js/library/modules/_wks.js": 244, + "./node_modules/core-js/library/modules/_object-gpo.js": 245, + "./node_modules/core-js/library/modules/web.dom.iterable.js": 246, + "./node_modules/core-js/library/modules/es6.array.iterator.js": 247, + "./node_modules/core-js/library/modules/_add-to-unscopables.js": 248, + "./node_modules/core-js/library/modules/_iter-step.js": 249, + "./node_modules/core-js/library/modules/_wks-ext.js": 250, + "./node_modules/babel-runtime/core-js/symbol.js": 251, + "./node_modules/core-js/library/fn/symbol/index.js": 252, + "./node_modules/core-js/library/modules/es6.symbol.js": 253, + "./node_modules/core-js/library/modules/_meta.js": 254, + "./node_modules/core-js/library/modules/_wks-define.js": 255, + "./node_modules/core-js/library/modules/_keyof.js": 256, + "./node_modules/core-js/library/modules/_enum-keys.js": 257, + "./node_modules/core-js/library/modules/_is-array.js": 258, + "./node_modules/core-js/library/modules/_object-gopn-ext.js": 259, + "./node_modules/core-js/library/modules/_object-gopn.js": 260, + "./node_modules/core-js/library/modules/_object-gopd.js": 261, + "./node_modules/core-js/library/modules/es6.object.to-string.js": 262, + "./node_modules/core-js/library/modules/es7.symbol.async-iterator.js": 263, + "./node_modules/core-js/library/modules/es7.symbol.observable.js": 264, + "./node_modules/babel-runtime/helpers/inherits.js": 265, + "./node_modules/babel-runtime/core-js/object/set-prototype-of.js": 266, + "./node_modules/core-js/library/fn/object/set-prototype-of.js": 267, + "./node_modules/core-js/library/modules/es6.object.set-prototype-of.js": 268, + "./node_modules/core-js/library/modules/_set-proto.js": 269, + "./node_modules/babel-runtime/core-js/object/create.js": 270, + "./node_modules/core-js/library/fn/object/create.js": 271, + "./node_modules/core-js/library/modules/es6.object.create.js": 272, + "./node_modules/react-bootstrap/lib/PanelGroup.js": 273, + "./node_modules/babel-runtime/helpers/objectWithoutProperties.js": 274, + "./node_modules/classnames/index.js": 275, + "./node_modules/react-bootstrap/lib/utils/bootstrapUtils.js": 276, + "./node_modules/babel-runtime/core-js/object/entries.js": 277, + "./node_modules/core-js/library/fn/object/entries.js": 278, + "./node_modules/core-js/library/modules/es7.object.entries.js": 279, + "./node_modules/core-js/library/modules/_object-to-array.js": 280, + "./node_modules/invariant/browser.js": 281, + "./node_modules/react-bootstrap/lib/utils/StyleConfig.js": 282, + "./node_modules/react-bootstrap/lib/utils/createChainedFunction.js": 283, + "./node_modules/react-bootstrap/lib/utils/ValidComponentChildren.js": 284, + "./node_modules/react-bootstrap/lib/Alert.js": 285, + "./node_modules/babel-runtime/core-js/object/values.js": 286, + "./node_modules/core-js/library/fn/object/values.js": 287, + "./node_modules/core-js/library/modules/es7.object.values.js": 288, + "./node_modules/react-bootstrap/lib/Badge.js": 289, + "./node_modules/react-bootstrap/lib/Breadcrumb.js": 290, + "./node_modules/react-bootstrap/lib/BreadcrumbItem.js": 291, + "./node_modules/react-bootstrap/lib/SafeAnchor.js": 292, + "./node_modules/react-prop-types/lib/elementType.js": 293, + "./node_modules/react-prop-types/lib/utils/createChainableTypeChecker.js": 294, + "./node_modules/react-bootstrap/lib/Button.js": 295, + "./node_modules/react-bootstrap/lib/ButtonGroup.js": 296, + "./node_modules/react-prop-types/lib/all.js": 297, + "./node_modules/react-bootstrap/lib/ButtonToolbar.js": 298, + "./node_modules/react-bootstrap/lib/Carousel.js": 299, + "./node_modules/react-bootstrap/lib/CarouselCaption.js": 300, + "./node_modules/react-bootstrap/lib/CarouselItem.js": 301, + "./node_modules/react-bootstrap/lib/utils/TransitionEvents.js": 302, + "./node_modules/react-bootstrap/lib/Glyphicon.js": 303, + "./node_modules/react-bootstrap/lib/Checkbox.js": 304, + "./node_modules/warning/browser.js": 305, + "./node_modules/react-bootstrap/lib/Clearfix.js": 306, + "./node_modules/react-bootstrap/lib/utils/capitalize.js": 307, + "./node_modules/react-bootstrap/lib/ControlLabel.js": 308, + "./node_modules/react-bootstrap/lib/Col.js": 309, + "./node_modules/react-bootstrap/lib/Collapse.js": 310, + "./node_modules/dom-helpers/style/index.js": 311, + "./node_modules/dom-helpers/util/camelizeStyle.js": 312, + "./node_modules/dom-helpers/util/camelize.js": 313, + "./node_modules/dom-helpers/util/hyphenateStyle.js": 314, + "./node_modules/dom-helpers/util/hyphenate.js": 315, + "./node_modules/dom-helpers/style/getComputedStyle.js": 316, + "./node_modules/dom-helpers/util/babelHelpers.js": 317, + "./node_modules/dom-helpers/style/removeStyle.js": 318, + "./node_modules/react-overlays/lib/Transition.js": 319, + "./node_modules/dom-helpers/transition/properties.js": 320, + "./node_modules/dom-helpers/util/inDOM.js": 321, + "./node_modules/dom-helpers/events/on.js": 322, + "./node_modules/react-bootstrap/lib/Dropdown.js": 323, + "./node_modules/dom-helpers/activeElement.js": 324, + "./node_modules/dom-helpers/ownerDocument.js": 325, + "./node_modules/dom-helpers/query/contains.js": 326, + "./node_modules/keycode/index.js": 327, + "./node_modules/react-prop-types/lib/isRequiredForA11y.js": 328, + "./node_modules/uncontrollable/index.js": 329, + "./node_modules/uncontrollable/createUncontrollable.js": 330, + "./node_modules/uncontrollable/utils.js": 331, + "./node_modules/react-bootstrap/lib/DropdownMenu.js": 332, + "./node_modules/babel-runtime/core-js/array/from.js": 333, + "./node_modules/core-js/library/fn/array/from.js": 334, + "./node_modules/core-js/library/modules/es6.array.from.js": 335, + "./node_modules/core-js/library/modules/_iter-call.js": 336, + "./node_modules/core-js/library/modules/_is-array-iter.js": 337, + "./node_modules/core-js/library/modules/_create-property.js": 338, + "./node_modules/core-js/library/modules/core.get-iterator-method.js": 339, + "./node_modules/core-js/library/modules/_classof.js": 340, + "./node_modules/core-js/library/modules/_iter-detect.js": 341, + "./node_modules/react-overlays/lib/RootCloseWrapper.js": 342, + "./node_modules/react-overlays/lib/utils/addEventListener.js": 343, + "./node_modules/dom-helpers/events/off.js": 344, + "./node_modules/react-overlays/lib/utils/ownerDocument.js": 345, + "./node_modules/react-bootstrap/lib/DropdownToggle.js": 346, + "./node_modules/react-bootstrap/lib/utils/PropTypes.js": 347, + "./node_modules/react-bootstrap/lib/DropdownButton.js": 348, + "./node_modules/react-bootstrap/lib/utils/splitComponentProps.js": 349, + "./node_modules/react-bootstrap/lib/Fade.js": 350, + "./node_modules/react-bootstrap/lib/Form.js": 351, + "./node_modules/react-bootstrap/lib/FormControl.js": 352, + "./node_modules/react-bootstrap/lib/FormControlFeedback.js": 353, + "./node_modules/react-bootstrap/lib/FormControlStatic.js": 354, + "./node_modules/react-bootstrap/lib/FormGroup.js": 355, + "./node_modules/react-bootstrap/lib/Grid.js": 356, + "./node_modules/react-bootstrap/lib/HelpBlock.js": 357, + "./node_modules/react-bootstrap/lib/Image.js": 358, + "./node_modules/react-bootstrap/lib/InputGroup.js": 359, + "./node_modules/react-bootstrap/lib/InputGroupAddon.js": 360, + "./node_modules/react-bootstrap/lib/InputGroupButton.js": 361, + "./node_modules/react-bootstrap/lib/Jumbotron.js": 362, + "./node_modules/react-bootstrap/lib/Label.js": 363, + "./node_modules/react-bootstrap/lib/ListGroup.js": 364, + "./node_modules/react-bootstrap/lib/ListGroupItem.js": 365, + "./node_modules/react-bootstrap/lib/Media.js": 366, + "./node_modules/react-bootstrap/lib/MediaBody.js": 367, + "./node_modules/react-bootstrap/lib/MediaHeading.js": 368, + "./node_modules/react-bootstrap/lib/MediaLeft.js": 369, + "./node_modules/react-bootstrap/lib/MediaList.js": 370, + "./node_modules/react-bootstrap/lib/MediaListItem.js": 371, + "./node_modules/react-bootstrap/lib/MediaRight.js": 372, + "./node_modules/react-bootstrap/lib/MenuItem.js": 373, + "./node_modules/react-bootstrap/lib/Modal.js": 374, + "./node_modules/dom-helpers/events/index.js": 375, + "./node_modules/dom-helpers/events/filter.js": 376, + "./node_modules/dom-helpers/query/querySelectorAll.js": 377, + "./node_modules/dom-helpers/util/scrollbarSize.js": 378, + "./node_modules/react-overlays/lib/Modal.js": 379, + "./node_modules/react-prop-types/lib/componentOrElement.js": 380, + "./node_modules/react-overlays/lib/Portal.js": 381, + "./node_modules/react-overlays/lib/utils/getContainer.js": 382, + "./node_modules/react-overlays/lib/ModalManager.js": 383, + "./node_modules/dom-helpers/class/index.js": 384, + "./node_modules/dom-helpers/class/addClass.js": 385, + "./node_modules/dom-helpers/class/hasClass.js": 386, + "./node_modules/dom-helpers/class/removeClass.js": 387, + "./node_modules/react-overlays/lib/utils/isOverflowing.js": 388, + "./node_modules/dom-helpers/query/isWindow.js": 389, + "./node_modules/react-overlays/lib/utils/manageAriaHidden.js": 390, + "./node_modules/react-overlays/lib/utils/addFocusListener.js": 391, + "./node_modules/react-bootstrap/lib/ModalBody.js": 392, + "./node_modules/react-bootstrap/lib/ModalDialog.js": 393, + "./node_modules/react-bootstrap/lib/ModalFooter.js": 394, + "./node_modules/react-bootstrap/lib/ModalHeader.js": 395, + "./node_modules/react-bootstrap/lib/ModalTitle.js": 396, + "./node_modules/react-bootstrap/lib/Nav.js": 397, + "./node_modules/react-bootstrap/lib/Navbar.js": 398, + "./node_modules/react-bootstrap/lib/NavbarBrand.js": 399, + "./node_modules/react-bootstrap/lib/NavbarCollapse.js": 400, + "./node_modules/react-bootstrap/lib/NavbarHeader.js": 401, + "./node_modules/react-bootstrap/lib/NavbarToggle.js": 402, + "./node_modules/react-bootstrap/lib/NavDropdown.js": 403, + "./node_modules/react-bootstrap/lib/NavItem.js": 404, + "./node_modules/react-bootstrap/lib/Overlay.js": 405, + "./node_modules/react-overlays/lib/Overlay.js": 406, + "./node_modules/react-overlays/lib/Position.js": 407, + "./node_modules/react-overlays/lib/utils/calculatePosition.js": 408, + "./node_modules/dom-helpers/query/offset.js": 409, + "./node_modules/dom-helpers/query/position.js": 410, + "./node_modules/dom-helpers/query/offsetParent.js": 411, + "./node_modules/dom-helpers/query/scrollTop.js": 412, + "./node_modules/dom-helpers/query/scrollLeft.js": 413, + "./node_modules/react-bootstrap/lib/OverlayTrigger.js": 414, + "./node_modules/react-bootstrap/lib/PageHeader.js": 415, + "./node_modules/react-bootstrap/lib/PageItem.js": 416, + "./node_modules/react-bootstrap/lib/PagerItem.js": 417, + "./node_modules/react-bootstrap/lib/utils/deprecationWarning.js": 418, + "./node_modules/react-bootstrap/lib/Pager.js": 419, + "./node_modules/react-bootstrap/lib/Pagination.js": 420, + "./node_modules/react-bootstrap/lib/PaginationButton.js": 421, + "./node_modules/react-bootstrap/lib/Panel.js": 422, + "./node_modules/react-bootstrap/lib/Popover.js": 423, + "./node_modules/react-bootstrap/lib/ProgressBar.js": 424, + "./node_modules/react-bootstrap/lib/Radio.js": 425, + "./node_modules/react-bootstrap/lib/ResponsiveEmbed.js": 426, + "./node_modules/react-bootstrap/lib/Row.js": 427, + "./node_modules/react-bootstrap/lib/SplitButton.js": 428, + "./node_modules/react-bootstrap/lib/SplitToggle.js": 429, + "./node_modules/react-bootstrap/lib/Tab.js": 430, + "./node_modules/react-bootstrap/lib/TabContainer.js": 431, + "./node_modules/react-bootstrap/lib/TabContent.js": 432, + "./node_modules/react-bootstrap/lib/TabPane.js": 433, + "./node_modules/react-bootstrap/lib/Table.js": 434, + "./node_modules/react-bootstrap/lib/Tabs.js": 435, + "./node_modules/react-bootstrap/lib/Thumbnail.js": 436, + "./node_modules/react-bootstrap/lib/Tooltip.js": 437, + "./node_modules/react-bootstrap/lib/Well.js": 438, + "./node_modules/react-bootstrap/lib/utils/index.js": 439, + "./node_modules/plotly.js/dist/plotly.js": 440, + "./node_modules/buffer/index.js": 441, + "./node_modules/base64-js/index.js": 442, + "./node_modules/ieee754/index.js": 443, + "./node_modules/isarray/index.js": 444, + "./node_modules/react-leaflet/lib/index.js": 445, + "./node_modules/leaflet/dist/leaflet-src.js": 446, + "./node_modules/react-leaflet/lib/types/index.js": 447, + "./node_modules/react-leaflet/lib/types/bounds.js": 448, + "./node_modules/react-leaflet/lib/types/latlngList.js": 449, + "./node_modules/react-leaflet/lib/types/latlng.js": 450, + "./node_modules/react-leaflet/lib/types/controlPosition.js": 451, + "./node_modules/react-leaflet/lib/AttributionControl.js": 452, + "./node_modules/react-leaflet/lib/MapControl.js": 453, + "./node_modules/react-leaflet/lib/BaseTileLayer.js": 454, + "./node_modules/react-leaflet/lib/MapLayer.js": 455, + "./node_modules/lodash/assign.js": 456, + "./node_modules/lodash/_assignValue.js": 457, + "./node_modules/lodash/_baseAssignValue.js": 458, + "./node_modules/lodash/_defineProperty.js": 459, + "./node_modules/lodash/_getNative.js": 460, + "./node_modules/lodash/_baseIsNative.js": 461, + "./node_modules/lodash/isFunction.js": 462, + "./node_modules/lodash/_baseGetTag.js": 463, + "./node_modules/lodash/_Symbol.js": 464, + "./node_modules/lodash/_root.js": 465, + "./node_modules/lodash/_freeGlobal.js": 466, + "./node_modules/lodash/_getRawTag.js": 467, + "./node_modules/lodash/_objectToString.js": 468, + "./node_modules/lodash/isObject.js": 469, + "./node_modules/lodash/_isMasked.js": 470, + "./node_modules/lodash/_coreJsData.js": 471, + "./node_modules/lodash/_toSource.js": 472, + "./node_modules/lodash/_getValue.js": 473, + "./node_modules/lodash/eq.js": 474, + "./node_modules/lodash/_copyObject.js": 475, + "./node_modules/lodash/_createAssigner.js": 476, + "./node_modules/lodash/_baseRest.js": 477, + "./node_modules/lodash/identity.js": 478, + "./node_modules/lodash/_overRest.js": 479, + "./node_modules/lodash/_apply.js": 480, + "./node_modules/lodash/_setToString.js": 481, + "./node_modules/lodash/_baseSetToString.js": 482, + "./node_modules/lodash/constant.js": 483, + "./node_modules/lodash/_shortOut.js": 484, + "./node_modules/lodash/_isIterateeCall.js": 485, + "./node_modules/lodash/isArrayLike.js": 486, + "./node_modules/lodash/isLength.js": 487, + "./node_modules/lodash/_isIndex.js": 488, + "./node_modules/lodash/_isPrototype.js": 489, + "./node_modules/lodash/keys.js": 490, + "./node_modules/lodash/_arrayLikeKeys.js": 491, + "./node_modules/lodash/_baseTimes.js": 492, + "./node_modules/lodash/isArguments.js": 493, + "./node_modules/lodash/_baseIsArguments.js": 494, + "./node_modules/lodash/isObjectLike.js": 495, + "./node_modules/lodash/isArray.js": 496, + "./node_modules/lodash/isBuffer.js": 497, + "./node_modules/webpack/buildin/module.js": 498, + "./node_modules/lodash/stubFalse.js": 499, + "./node_modules/lodash/isTypedArray.js": 500, + "./node_modules/lodash/_baseIsTypedArray.js": 501, + "./node_modules/lodash/_baseUnary.js": 502, + "./node_modules/lodash/_nodeUtil.js": 503, + "./node_modules/lodash/_baseKeys.js": 504, + "./node_modules/lodash/_nativeKeys.js": 505, + "./node_modules/lodash/_overArg.js": 506, + "./node_modules/react-leaflet/lib/MapComponent.js": 507, + "./node_modules/lodash/clone.js": 508, + "./node_modules/lodash/_baseClone.js": 509, + "./node_modules/lodash/_Stack.js": 510, + "./node_modules/lodash/_ListCache.js": 511, + "./node_modules/lodash/_listCacheClear.js": 512, + "./node_modules/lodash/_listCacheDelete.js": 513, + "./node_modules/lodash/_assocIndexOf.js": 514, + "./node_modules/lodash/_listCacheGet.js": 515, + "./node_modules/lodash/_listCacheHas.js": 516, + "./node_modules/lodash/_listCacheSet.js": 517, + "./node_modules/lodash/_stackClear.js": 518, + "./node_modules/lodash/_stackDelete.js": 519, + "./node_modules/lodash/_stackGet.js": 520, + "./node_modules/lodash/_stackHas.js": 521, + "./node_modules/lodash/_stackSet.js": 522, + "./node_modules/lodash/_Map.js": 523, + "./node_modules/lodash/_MapCache.js": 524, + "./node_modules/lodash/_mapCacheClear.js": 525, + "./node_modules/lodash/_Hash.js": 526, + "./node_modules/lodash/_hashClear.js": 527, + "./node_modules/lodash/_nativeCreate.js": 528, + "./node_modules/lodash/_hashDelete.js": 529, + "./node_modules/lodash/_hashGet.js": 530, + "./node_modules/lodash/_hashHas.js": 531, + "./node_modules/lodash/_hashSet.js": 532, + "./node_modules/lodash/_mapCacheDelete.js": 533, + "./node_modules/lodash/_getMapData.js": 534, + "./node_modules/lodash/_isKeyable.js": 535, + "./node_modules/lodash/_mapCacheGet.js": 536, + "./node_modules/lodash/_mapCacheHas.js": 537, + "./node_modules/lodash/_mapCacheSet.js": 538, + "./node_modules/lodash/_arrayEach.js": 539, + "./node_modules/lodash/_baseAssign.js": 540, + "./node_modules/lodash/_baseAssignIn.js": 541, + "./node_modules/lodash/keysIn.js": 542, + "./node_modules/lodash/_baseKeysIn.js": 543, + "./node_modules/lodash/_nativeKeysIn.js": 544, + "./node_modules/lodash/_cloneBuffer.js": 545, + "./node_modules/lodash/_copyArray.js": 546, + "./node_modules/lodash/_copySymbols.js": 547, + "./node_modules/lodash/_getSymbols.js": 548, + "./node_modules/lodash/stubArray.js": 549, + "./node_modules/lodash/_copySymbolsIn.js": 550, + "./node_modules/lodash/_getSymbolsIn.js": 551, + "./node_modules/lodash/_arrayPush.js": 552, + "./node_modules/lodash/_getPrototype.js": 553, + "./node_modules/lodash/_getAllKeys.js": 554, + "./node_modules/lodash/_baseGetAllKeys.js": 555, + "./node_modules/lodash/_getAllKeysIn.js": 556, + "./node_modules/lodash/_getTag.js": 557, + "./node_modules/lodash/_DataView.js": 558, + "./node_modules/lodash/_Promise.js": 559, + "./node_modules/lodash/_Set.js": 560, + "./node_modules/lodash/_WeakMap.js": 561, + "./node_modules/lodash/_initCloneArray.js": 562, + "./node_modules/lodash/_initCloneByTag.js": 563, + "./node_modules/lodash/_cloneArrayBuffer.js": 564, + "./node_modules/lodash/_Uint8Array.js": 565, + "./node_modules/lodash/_cloneDataView.js": 566, + "./node_modules/lodash/_cloneMap.js": 567, + "./node_modules/lodash/_addMapEntry.js": 568, + "./node_modules/lodash/_arrayReduce.js": 569, + "./node_modules/lodash/_mapToArray.js": 570, + "./node_modules/lodash/_cloneRegExp.js": 571, + "./node_modules/lodash/_cloneSet.js": 572, + "./node_modules/lodash/_addSetEntry.js": 573, + "./node_modules/lodash/_setToArray.js": 574, + "./node_modules/lodash/_cloneSymbol.js": 575, + "./node_modules/lodash/_cloneTypedArray.js": 576, + "./node_modules/lodash/_initCloneObject.js": 577, + "./node_modules/lodash/_baseCreate.js": 578, + "./node_modules/lodash/forEach.js": 579, + "./node_modules/lodash/_baseEach.js": 580, + "./node_modules/lodash/_baseForOwn.js": 581, + "./node_modules/lodash/_baseFor.js": 582, + "./node_modules/lodash/_createBaseFor.js": 583, + "./node_modules/lodash/_createBaseEach.js": 584, + "./node_modules/lodash/_castFunction.js": 585, + "./node_modules/lodash/reduce.js": 586, + "./node_modules/lodash/_baseIteratee.js": 587, + "./node_modules/lodash/_baseMatches.js": 588, + "./node_modules/lodash/_baseIsMatch.js": 589, + "./node_modules/lodash/_baseIsEqual.js": 590, + "./node_modules/lodash/_baseIsEqualDeep.js": 591, + "./node_modules/lodash/_equalArrays.js": 592, + "./node_modules/lodash/_SetCache.js": 593, + "./node_modules/lodash/_setCacheAdd.js": 594, + "./node_modules/lodash/_setCacheHas.js": 595, + "./node_modules/lodash/_arraySome.js": 596, + "./node_modules/lodash/_cacheHas.js": 597, + "./node_modules/lodash/_equalByTag.js": 598, + "./node_modules/lodash/_equalObjects.js": 599, + "./node_modules/lodash/_getMatchData.js": 600, + "./node_modules/lodash/_isStrictComparable.js": 601, + "./node_modules/lodash/_matchesStrictComparable.js": 602, + "./node_modules/lodash/_baseMatchesProperty.js": 603, + "./node_modules/lodash/get.js": 604, + "./node_modules/lodash/_baseGet.js": 605, + "./node_modules/lodash/_castPath.js": 606, + "./node_modules/lodash/_isKey.js": 607, + "./node_modules/lodash/isSymbol.js": 608, + "./node_modules/lodash/_stringToPath.js": 609, + "./node_modules/lodash/_memoizeCapped.js": 610, + "./node_modules/lodash/memoize.js": 611, + "./node_modules/lodash/toString.js": 612, + "./node_modules/lodash/_baseToString.js": 613, + "./node_modules/lodash/_arrayMap.js": 614, + "./node_modules/lodash/_toKey.js": 615, + "./node_modules/lodash/hasIn.js": 616, + "./node_modules/lodash/_baseHasIn.js": 617, + "./node_modules/lodash/_hasPath.js": 618, + "./node_modules/lodash/property.js": 619, + "./node_modules/lodash/_baseProperty.js": 620, + "./node_modules/lodash/_basePropertyDeep.js": 621, + "./node_modules/lodash/_baseReduce.js": 622, + "./node_modules/react-leaflet/lib/CanvasTileLayer.js": 623, + "./node_modules/react-leaflet/lib/Circle.js": 624, + "./node_modules/react-leaflet/lib/Path.js": 625, + "./node_modules/lodash/isEqual.js": 626, + "./node_modules/lodash/pick.js": 627, + "./node_modules/lodash/_basePick.js": 628, + "./node_modules/lodash/_basePickBy.js": 629, + "./node_modules/lodash/_baseSet.js": 630, + "./node_modules/lodash/_flatRest.js": 631, + "./node_modules/lodash/flatten.js": 632, + "./node_modules/lodash/_baseFlatten.js": 633, + "./node_modules/lodash/_isFlattenable.js": 634, + "./node_modules/react-leaflet/lib/CircleMarker.js": 635, + "./node_modules/react-leaflet/lib/FeatureGroup.js": 636, + "./node_modules/react-leaflet/lib/GeoJson.js": 637, + "./node_modules/react-leaflet/lib/ImageOverlay.js": 638, + "./node_modules/react-leaflet/lib/LayerGroup.js": 639, + "./node_modules/react-leaflet/lib/LayersControl.js": 640, + "./node_modules/react-leaflet/lib/Map.js": 641, + "./node_modules/lodash/isUndefined.js": 642, + "./node_modules/lodash/uniqueId.js": 643, + "./node_modules/react-leaflet/lib/Marker.js": 644, + "./node_modules/react-leaflet/lib/MultiPolygon.js": 645, + "./node_modules/react-leaflet/lib/MultiPolyline.js": 646, + "./node_modules/react-leaflet/lib/Polygon.js": 647, + "./node_modules/react-leaflet/lib/Polyline.js": 648, + "./node_modules/react-leaflet/lib/Popup.js": 649, + "./node_modules/react-leaflet/lib/Rectangle.js": 650, + "./node_modules/react-leaflet/lib/ScaleControl.js": 651, + "./node_modules/react-leaflet/lib/TileLayer.js": 652, + "./node_modules/react-leaflet/lib/WMSTileLayer.js": 653, + "./node_modules/react-leaflet/lib/ZoomControl.js": 654, + "./node_modules/plotly.js/lib/core.js": 655, + "./node_modules/plotly.js/src/core.js": 656, + "./node_modules/plotly.js/src/plotly.js": 657, + "./node_modules/plotly.js/src/plot_api/plot_config.js": 658, + "./node_modules/plotly.js/src/plots/plots.js": 659, + "./node_modules/d3/d3.js": 660, + "./node_modules/fast-isnumeric/index.js": 661, + "./node_modules/plotly.js/src/plot_api/plot_schema.js": 662, + "./node_modules/plotly.js/src/registry.js": 663, + "./node_modules/plotly.js/src/lib/loggers.js": 664, + "./node_modules/plotly.js/src/lib/noop.js": 665, + "./node_modules/plotly.js/src/lib/push_unique.js": 666, + "./node_modules/plotly.js/src/plots/attributes.js": 667, + "./node_modules/plotly.js/src/components/fx/attributes.js": 668, + "./node_modules/plotly.js/src/lib/extend.js": 669, + "./node_modules/plotly.js/src/lib/is_plain_object.js": 670, + "./node_modules/plotly.js/src/plots/font_attributes.js": 671, + "./node_modules/plotly.js/src/lib/index.js": 672, + "./node_modules/plotly.js/src/constants/numerical.js": 673, + "./node_modules/plotly.js/src/lib/nested_property.js": 674, + "./node_modules/plotly.js/src/lib/is_array.js": 675, + "./node_modules/plotly.js/src/plot_api/container_array_match.js": 676, + "./node_modules/plotly.js/src/lib/mod.js": 677, + "./node_modules/plotly.js/src/lib/to_log_range.js": 678, + "./node_modules/plotly.js/src/lib/relink_private.js": 679, + "./node_modules/plotly.js/src/lib/ensure_array.js": 680, + "./node_modules/plotly.js/src/lib/coerce.js": 681, + "./node_modules/tinycolor2/tinycolor.js": 682, + "./node_modules/plotly.js/src/components/colorscale/get_scale.js": 683, + "./node_modules/plotly.js/src/components/colorscale/scales.js": 684, + "./node_modules/plotly.js/src/components/colorscale/default_scale.js": 685, + "./node_modules/plotly.js/src/components/colorscale/is_valid_scale_array.js": 686, + "./node_modules/plotly.js/src/lib/dates.js": 687, + "./node_modules/plotly.js/src/lib/search.js": 688, + "./node_modules/plotly.js/src/lib/stats.js": 689, + "./node_modules/plotly.js/src/lib/matrix.js": 690, + "./node_modules/plotly.js/src/lib/notifier.js": 691, + "./node_modules/plotly.js/src/lib/filter_unique.js": 692, + "./node_modules/plotly.js/src/lib/filter_visible.js": 693, + "./node_modules/plotly.js/src/lib/clean_number.js": 694, + "./node_modules/plotly.js/src/lib/identity.js": 695, + "./node_modules/plotly.js/src/plots/layout_attributes.js": 696, + "./node_modules/plotly.js/src/components/color/attributes.js": 697, + "./node_modules/plotly.js/src/plots/frame_attributes.js": 698, + "./node_modules/plotly.js/src/plots/animation_attributes.js": 699, + "./node_modules/plotly.js/src/plots/polar/area_attributes.js": 700, + "./node_modules/plotly.js/src/traces/scatter/attributes.js": 701, + "./node_modules/plotly.js/src/components/colorscale/color_attributes.js": 702, + "./node_modules/plotly.js/src/components/colorscale/attributes.js": 703, + "./node_modules/plotly.js/src/components/errorbars/attributes.js": 704, + "./node_modules/plotly.js/src/components/colorbar/attributes.js": 705, + "./node_modules/plotly.js/src/plots/cartesian/layout_attributes.js": 706, + "./node_modules/plotly.js/src/components/drawing/attributes.js": 707, + "./node_modules/plotly.js/src/plots/cartesian/constants.js": 708, + "./node_modules/plotly.js/src/components/drawing/index.js": 709, + "./node_modules/plotly.js/src/components/color/index.js": 710, + "./node_modules/plotly.js/src/components/colorscale/index.js": 711, + "./node_modules/plotly.js/src/components/colorscale/defaults.js": 712, + "./node_modules/plotly.js/src/components/colorbar/has_colorbar.js": 713, + "./node_modules/plotly.js/src/components/colorbar/defaults.js": 714, + "./node_modules/plotly.js/src/plots/cartesian/tick_value_defaults.js": 715, + "./node_modules/plotly.js/src/plots/cartesian/tick_mark_defaults.js": 716, + "./node_modules/plotly.js/src/plots/cartesian/tick_label_defaults.js": 717, + "./node_modules/plotly.js/src/components/colorscale/is_valid_scale.js": 718, + "./node_modules/plotly.js/src/components/colorscale/flip_scale.js": 719, + "./node_modules/plotly.js/src/components/colorscale/calc.js": 720, + "./node_modules/plotly.js/src/components/colorscale/has_colorscale.js": 721, + "./node_modules/plotly.js/src/components/colorscale/extract_scale.js": 722, + "./node_modules/plotly.js/src/components/colorscale/make_color_scale_func.js": 723, + "./node_modules/plotly.js/src/lib/svg_text_utils.js": 724, + "./node_modules/plotly.js/src/constants/xmlns_namespaces.js": 725, + "./node_modules/plotly.js/src/constants/string_mappings.js": 726, + "./node_modules/plotly.js/src/constants/alignment.js": 727, + "./node_modules/plotly.js/src/traces/scatter/subtypes.js": 728, + "./node_modules/plotly.js/src/traces/scatter/make_bubble_size_func.js": 729, + "./node_modules/plotly.js/src/components/drawing/symbol_defs.js": 730, + "./node_modules/plotly.js/src/traces/scatter/constants.js": 731, + "./node_modules/plotly.js/src/plots/polar/axis_attributes.js": 732, + "./node_modules/plotly.js/src/components/errorbars/index.js": 733, + "./node_modules/plotly.js/src/components/errorbars/defaults.js": 734, + "./node_modules/plotly.js/src/components/errorbars/calc.js": 735, + "./node_modules/plotly.js/src/plots/cartesian/axes.js": 736, + "./node_modules/plotly.js/src/components/titles/index.js": 737, + "./node_modules/plotly.js/src/constants/interactions.js": 738, + "./node_modules/plotly.js/src/plots/cartesian/layout_defaults.js": 739, + "./node_modules/plotly.js/src/plots/cartesian/type_defaults.js": 740, + "./node_modules/plotly.js/src/plots/cartesian/axis_autotype.js": 741, + "./node_modules/plotly.js/src/plots/cartesian/axis_ids.js": 742, + "./node_modules/plotly.js/src/plots/cartesian/axis_defaults.js": 743, + "./node_modules/plotly.js/src/plots/cartesian/category_order_defaults.js": 744, + "./node_modules/plotly.js/src/plots/cartesian/set_convert.js": 745, + "./node_modules/plotly.js/src/plots/cartesian/ordered_categories.js": 746, + "./node_modules/plotly.js/src/plots/cartesian/constraint_defaults.js": 747, + "./node_modules/plotly.js/src/plots/cartesian/position_defaults.js": 748, + "./node_modules/plotly.js/src/components/errorbars/compute_error.js": 749, + "./node_modules/plotly.js/src/components/errorbars/plot.js": 750, + "./node_modules/plotly.js/src/components/errorbars/style.js": 751, + "./node_modules/plotly.js/src/plots/command.js": 752, + "./node_modules/plotly.js/src/components/modebar/index.js": 753, + "./node_modules/plotly.js/src/components/modebar/manage.js": 754, + "./node_modules/plotly.js/src/components/modebar/modebar.js": 755, + "./node_modules/plotly.js/build/ploticon.js": 756, + "./node_modules/plotly.js/src/components/modebar/buttons.js": 757, + "./node_modules/plotly.js/src/snapshot/download.js": 758, + "./node_modules/plotly.js/src/plot_api/to_image.js": 759, + "./node_modules/plotly.js/src/snapshot/helpers.js": 760, + "./node_modules/plotly.js/src/snapshot/cloneplot.js": 761, + "./node_modules/plotly.js/src/snapshot/tosvg.js": 762, + "./node_modules/plotly.js/src/snapshot/svgtoimg.js": 763, + "./node_modules/events/events.js": 764, + "./node_modules/plotly.js/src/snapshot/filesaver.js": 765, + "./node_modules/plotly.js/src/plot_api/plot_api.js": 766, + "./node_modules/plotly.js/src/lib/events.js": 767, + "./node_modules/plotly.js/src/lib/queue.js": 768, + "./node_modules/plotly.js/src/plots/polar/index.js": 769, + "./node_modules/plotly.js/src/plots/polar/micropolar.js": 770, + "./node_modules/plotly.js/src/plots/polar/micropolar_manager.js": 771, + "./node_modules/plotly.js/src/plots/polar/undo_manager.js": 772, + "./node_modules/plotly.js/src/plots/cartesian/graph_interact.js": 773, + "./node_modules/plotly.js/src/components/fx/index.js": 774, + "./node_modules/plotly.js/src/components/dragelement/index.js": 775, + "./node_modules/plotly.js/src/components/dragelement/align.js": 776, + "./node_modules/plotly.js/src/components/dragelement/cursor.js": 777, + "./node_modules/plotly.js/src/components/dragelement/unhover.js": 778, + "./node_modules/plotly.js/src/components/fx/helpers.js": 779, + "./node_modules/plotly.js/src/components/fx/constants.js": 780, + "./node_modules/plotly.js/src/components/fx/layout_attributes.js": 781, + "./node_modules/plotly.js/src/components/fx/layout_global_defaults.js": 782, + "./node_modules/plotly.js/src/components/fx/hoverlabel_defaults.js": 783, + "./node_modules/plotly.js/src/components/fx/defaults.js": 784, + "./node_modules/plotly.js/src/components/fx/layout_defaults.js": 785, + "./node_modules/plotly.js/src/components/fx/calc.js": 786, + "./node_modules/plotly.js/src/components/fx/hover.js": 787, + "./node_modules/plotly.js/src/lib/override_cursor.js": 788, + "./node_modules/plotly.js/src/lib/setcursor.js": 789, + "./node_modules/plotly.js/src/components/fx/click.js": 790, + "./node_modules/plotly.js/src/plots/cartesian/dragbox.js": 791, + "./node_modules/plotly.js/src/plots/cartesian/select.js": 792, + "./node_modules/plotly.js/src/lib/polygon.js": 793, + "./node_modules/plotly.js/src/plots/cartesian/scale_zoom.js": 794, + "./node_modules/plotly.js/src/plot_api/manage_arrays.js": 795, + "./node_modules/plotly.js/src/plot_api/helpers.js": 796, + "./node_modules/gl-mat4/fromQuat.js": 797, + "./node_modules/plotly.js/src/plot_api/subroutines.js": 798, + "./node_modules/plotly.js/src/plots/cartesian/constraints.js": 799, + "./node_modules/plotly.js/node_modules/es6-promise/dist/es6-promise.js": 800, + "./node_modules/plotly.js/build/plotcss.js": 802, + "./node_modules/plotly.js/src/fonts/mathjax_config.js": 803, + "./node_modules/plotly.js/src/plot_api/set_plot_config.js": 804, + "./node_modules/plotly.js/src/plot_api/register.js": 805, + "./node_modules/plotly.js/src/plot_api/validate.js": 806, + "./node_modules/plotly.js/src/traces/scatter/index.js": 807, + "./node_modules/plotly.js/src/traces/scatter/defaults.js": 808, + "./node_modules/plotly.js/src/traces/scatter/xy_defaults.js": 809, + "./node_modules/plotly.js/src/traces/scatter/marker_defaults.js": 810, + "./node_modules/plotly.js/src/traces/scatter/line_defaults.js": 811, + "./node_modules/plotly.js/src/traces/scatter/line_shape_defaults.js": 812, + "./node_modules/plotly.js/src/traces/scatter/text_defaults.js": 813, + "./node_modules/plotly.js/src/traces/scatter/fillcolor_defaults.js": 814, + "./node_modules/plotly.js/src/traces/scatter/clean_data.js": 815, + "./node_modules/plotly.js/src/traces/scatter/calc.js": 816, + "./node_modules/plotly.js/src/traces/scatter/colorscale_calc.js": 817, + "./node_modules/plotly.js/src/traces/scatter/arrays_to_calcdata.js": 818, + "./node_modules/plotly.js/src/traces/scatter/plot.js": 819, + "./node_modules/plotly.js/src/traces/scatter/line_points.js": 820, + "./node_modules/plotly.js/src/traces/scatter/link_traces.js": 821, + "./node_modules/plotly.js/src/traces/scatter/colorbar.js": 822, + "./node_modules/plotly.js/src/components/colorbar/draw.js": 823, + "./node_modules/plotly.js/src/traces/scatter/style.js": 824, + "./node_modules/plotly.js/src/traces/scatter/hover.js": 825, + "./node_modules/plotly.js/src/traces/scatter/get_trace_color.js": 826, + "./node_modules/plotly.js/src/traces/scatter/select.js": 827, + "./node_modules/plotly.js/src/plots/cartesian/index.js": 828, + "./node_modules/plotly.js/src/plots/cartesian/attributes.js": 829, + "./node_modules/plotly.js/src/plots/cartesian/transition_axes.js": 830, + "./node_modules/plotly.js/src/components/legend/index.js": 831, + "./node_modules/plotly.js/src/components/legend/attributes.js": 832, + "./node_modules/plotly.js/src/components/legend/defaults.js": 833, + "./node_modules/plotly.js/src/components/legend/helpers.js": 834, + "./node_modules/plotly.js/src/components/legend/draw.js": 835, + "./node_modules/plotly.js/src/components/legend/constants.js": 836, + "./node_modules/plotly.js/src/components/legend/get_legend_data.js": 837, + "./node_modules/plotly.js/src/components/legend/style.js": 838, + "./node_modules/plotly.js/src/traces/pie/style_one.js": 839, + "./node_modules/plotly.js/src/components/legend/anchor_utils.js": 840, + "./node_modules/plotly.js/src/components/annotations/index.js": 841, + "./node_modules/plotly.js/src/components/annotations/draw.js": 842, + "./node_modules/plotly.js/src/components/annotations/draw_arrow_head.js": 843, + "./node_modules/plotly.js/src/components/annotations/arrow_paths.js": 844, + "./node_modules/plotly.js/src/components/annotations/click.js": 845, + "./node_modules/plotly.js/src/components/annotations/attributes.js": 846, + "./node_modules/plotly.js/src/components/annotations/defaults.js": 847, + "./node_modules/plotly.js/src/plots/array_container_defaults.js": 848, + "./node_modules/plotly.js/src/components/annotations/annotation_defaults.js": 849, + "./node_modules/plotly.js/src/components/annotations/common_defaults.js": 850, + "./node_modules/plotly.js/src/components/annotations/calc_autorange.js": 851, + "./node_modules/plotly.js/src/components/annotations/convert_coords.js": 852, + "./node_modules/plotly.js/src/components/annotations3d/index.js": 853, + "./node_modules/plotly.js/src/components/annotations3d/attributes.js": 854, + "./node_modules/plotly.js/src/components/annotations3d/defaults.js": 855, + "./node_modules/plotly.js/src/components/annotations3d/convert.js": 856, + "./node_modules/plotly.js/src/components/annotations3d/draw.js": 857, + "./node_modules/plotly.js/src/plots/gl3d/project.js": 858, + "./node_modules/plotly.js/src/components/shapes/index.js": 859, + "./node_modules/plotly.js/src/components/shapes/draw.js": 860, + "./node_modules/plotly.js/src/components/shapes/constants.js": 861, + "./node_modules/plotly.js/src/components/shapes/helpers.js": 862, + "./node_modules/plotly.js/src/components/shapes/attributes.js": 863, + "./node_modules/plotly.js/src/components/shapes/defaults.js": 864, + "./node_modules/plotly.js/src/components/shapes/shape_defaults.js": 865, + "./node_modules/plotly.js/src/components/shapes/calc_autorange.js": 866, + "./node_modules/plotly.js/src/components/images/index.js": 867, + "./node_modules/plotly.js/src/components/images/attributes.js": 868, + "./node_modules/plotly.js/src/components/images/defaults.js": 869, + "./node_modules/plotly.js/src/components/images/draw.js": 870, + "./node_modules/plotly.js/src/components/images/convert_coords.js": 871, + "./node_modules/plotly.js/src/components/updatemenus/index.js": 872, + "./node_modules/plotly.js/src/components/updatemenus/constants.js": 873, + "./node_modules/plotly.js/src/components/updatemenus/attributes.js": 874, + "./node_modules/plotly.js/src/plots/pad_attributes.js": 875, + "./node_modules/plotly.js/src/components/updatemenus/defaults.js": 876, + "./node_modules/plotly.js/src/components/updatemenus/draw.js": 877, + "./node_modules/plotly.js/src/components/updatemenus/scrollbox.js": 878, + "./node_modules/plotly.js/src/components/sliders/index.js": 879, + "./node_modules/plotly.js/src/components/sliders/constants.js": 880, + "./node_modules/plotly.js/src/components/sliders/attributes.js": 881, + "./node_modules/plotly.js/src/components/sliders/defaults.js": 882, + "./node_modules/plotly.js/src/components/sliders/draw.js": 883, + "./node_modules/plotly.js/src/components/rangeslider/index.js": 884, + "./node_modules/plotly.js/src/components/rangeslider/attributes.js": 885, + "./node_modules/plotly.js/src/components/rangeslider/defaults.js": 886, + "./node_modules/plotly.js/src/components/rangeslider/calc_autorange.js": 887, + "./node_modules/plotly.js/src/components/rangeslider/constants.js": 888, + "./node_modules/plotly.js/src/components/rangeslider/draw.js": 889, + "./node_modules/plotly.js/src/components/rangeselector/index.js": 890, + "./node_modules/plotly.js/src/components/rangeselector/attributes.js": 891, + "./node_modules/plotly.js/src/components/rangeselector/button_attributes.js": 892, + "./node_modules/plotly.js/src/components/rangeselector/defaults.js": 893, + "./node_modules/plotly.js/src/components/rangeselector/constants.js": 894, + "./node_modules/plotly.js/src/components/rangeselector/draw.js": 895, + "./node_modules/plotly.js/src/components/rangeselector/get_update_object.js": 896, + "./node_modules/plotly.js/src/snapshot/index.js": 897, + "./node_modules/plotly.js/src/snapshot/toimage.js": 898, + "./node_modules/plotly.js/lib/bar.js": 899, + "./node_modules/plotly.js/src/traces/bar/index.js": 900, + "./node_modules/plotly.js/src/traces/bar/attributes.js": 901, + "./node_modules/plotly.js/src/traces/bar/layout_attributes.js": 902, + "./node_modules/plotly.js/src/traces/bar/defaults.js": 903, + "./node_modules/plotly.js/src/traces/bar/style_defaults.js": 904, + "./node_modules/plotly.js/src/traces/bar/layout_defaults.js": 905, + "./node_modules/plotly.js/src/traces/bar/calc.js": 906, + "./node_modules/plotly.js/src/traces/bar/arrays_to_calcdata.js": 907, + "./node_modules/plotly.js/src/traces/bar/set_positions.js": 908, + "./node_modules/plotly.js/src/traces/bar/sieve.js": 909, + "./node_modules/plotly.js/src/traces/bar/plot.js": 910, + "./node_modules/plotly.js/src/traces/bar/style.js": 911, + "./node_modules/plotly.js/src/traces/bar/hover.js": 912, + "./node_modules/plotly.js/lib/pie.js": 913, + "./node_modules/plotly.js/src/traces/pie/index.js": 914, + "./node_modules/plotly.js/src/traces/pie/attributes.js": 915, + "./node_modules/plotly.js/src/traces/pie/defaults.js": 916, + "./node_modules/plotly.js/src/traces/pie/layout_defaults.js": 917, + "./node_modules/plotly.js/src/traces/pie/layout_attributes.js": 918, + "./node_modules/plotly.js/src/traces/pie/calc.js": 919, + "./node_modules/plotly.js/src/traces/pie/helpers.js": 920, + "./node_modules/plotly.js/src/traces/pie/plot.js": 921, + "./node_modules/plotly.js/src/traces/pie/style.js": 922, + "./node_modules/plotly.js/src/traces/pie/base_plot.js": 923, + "./node_modules/immutable/dist/immutable.js": 924 + } +} \ No newline at end of file diff --git a/ui/oce-standalone/index.jsx b/ui/oce-standalone/index.jsx index 6b64e8cae..a036db5a5 100644 --- a/ui/oce-standalone/index.jsx +++ b/ui/oce-standalone/index.jsx @@ -1,21 +1,28 @@ -import ReactDOM from "react-dom"; -import OCApp from "../oce"; +import { Map } from 'immutable'; +import cn from 'classnames'; +import ReactDOM from 'react-dom'; +// eslint-disable-next-line no-unused-vars +import styles from './style.less'; +import OCApp from '../oce'; import OverviewTab from '../oce/tabs/overview'; import LocationTab from '../oce/tabs/location'; import CompetitivenessTab from '../oce/tabs/competitiveness'; import EfficiencyTab from '../oce/tabs/efficiency'; import EProcurementTab from '../oce/tabs/e-procurement'; -import {fetchJson} from "../oce/tools"; -import {Map} from "immutable"; -import styles from "./style.less"; -import ViewSwitcher from "../oce/switcher.jsx"; -import CorruptionRickDashboard from "../oce/corruption-risk"; -import cn from "classnames"; - -class OCEDemoLocation extends LocationTab{} +import { fetchJson } from '../oce/tools'; +import ViewSwitcher from '../oce/switcher.jsx'; +import CorruptionRickDashboard from '../oce/corruption-risk'; + +class OCEDemoLocation extends LocationTab { + getHeight() { + const TOP_OFFSET = 128; + const BOTTOM_OFFSET = 66; + return window.innerHeight - TOP_OFFSET - BOTTOM_OFFSET; + } +} OCEDemoLocation.CENTER = [37, -100]; -class OCEChild extends OCApp{ +class OCEChild extends OCApp { constructor(props) { super(props); this.registerTab(OverviewTab); @@ -25,7 +32,7 @@ class OCEChild extends OCApp{ this.registerTab(EProcurementTab); } - fetchBidTypes(){ + fetchBidTypes() { fetchJson('/api/ocds/bidType/all').then(data => this.setState({ bidTypes: data.reduce((map, datum) => @@ -34,103 +41,127 @@ class OCEChild extends OCApp{ ); } - loginBox(){ - let linkUrl, text; - if(this.state.user.loggedIn){ - linkUrl = "/preLogout?referrer=/ui/index.html" - text = this.t("general:logout"); + loginBox() { + let linkUrl; + let text; + if (this.state.user.loggedIn) { + linkUrl = '/preLogout?referrer=/ui/index.html'; + text = this.t('general:logout'); } else { - linkUrl = "/login?referrer=/ui/index.html"; - text = this.t("general:login"); + linkUrl = '/login?referrer=/ui/index.html'; + text = this.t('general:login'); } - return - - + return ( + + + + ); } - dashboardSwitcher(){ - const {dashboardSwitcherOpen} = this.state; - const {onSwitch} = this.props; + dashboardSwitcher() { + const { dashboardSwitcherOpen } = this.state; + const { onSwitch } = this.props; return ( -
+

Monitoring & Evaluation Toolkit - +

{dashboardSwitcherOpen && }
- ) + ); } - exportBtn(){ - if(this.state.exporting){ + exportBtn() { + if(this.state.exporting) { return (
-
+
{this.t('export:exporting')}
- ) + ); } return (
- ) + ); + } + + languageSwitcher() { + const { TRANSLATIONS } = this.constructor; + const { locale: selectedLocale } = this.state; + if (Object.keys(TRANSLATIONS).length <= 1) return null; + return Object.keys(TRANSLATIONS).map(locale => ( + this.setLocale(locale)} + className={cn({ active: locale === selectedLocale })} + > + {locale.split('_')[0]} + + )); } navigationLink(Tab, index){ - if(OverviewTab != Tab) return super.navigationLink(Tab, index); - const {getName, icon} = Tab; + if (OverviewTab !== Tab) return super.navigationLink(Tab, index); + const { getName, icon } = Tab; return (
this.setState({currentTab: index})} > - - + + -   - {getName(this.t.bind(this))} - +   + {getName(this.t.bind(this))} +
- The Procurement M&E Prototype is an interactive platform for analyzing, monitoring, and evaluating information on public procurement. It is specifically designed to help users understand procurement efficiency, and the competitiveness and cost-effectiveness of public markets. + The Procurement M&E Prototype is an interactive platform for analyzing, monitoring, + and evaluating information on public procurement. It is specifically designed to help + users understand procurement efficiency, and the competitiveness and cost-effectiveness + of public markets.
- ) + ); } render(){ return ( -
this.setState({menuBox: ""})}> +
this.setState({ menuBox: '' })}>
- + {this.dashboardSwitcher()}
-
+
+ {this.languageSwitcher()} +
+
{this.loginBox()}
- Filter your data + {this.t('filters:hint')}
{this.filters()} {this.comparison()} @@ -161,10 +192,16 @@ class OCEChild extends OCApp{ {this.content()}
- {this.showMonths() &&
+ {this.showMonths() &&
{this.monthsBar()}
} -
+
{this.yearsBar()}
 
@@ -174,7 +211,9 @@ class OCEChild extends OCApp{ } const translations = { - en_US: require('../../web/public/languages/en_US.json') + en_US: require('../../web/public/languages/en_US.json'), + es_ES: require('../../web/public/languages/es_ES.json'), + fr_FR: require('../../web/public/languages/fr_FR.json'), }; const BILLION = 1000000000; @@ -184,21 +223,21 @@ const formatNumber = number => number.toLocaleString(undefined, {maximumFraction const styling = { charts: { - axisLabelColor: "#cc3c3b", + axisLabelColor: '#cc3c3b', traceColors: ['#324d6e', '#ecac00', '#4b6f33'], hoverFormat: ',.2f', - hoverFormatter: number => { - if(typeof number == "undefined") return number; + hoverFormatter: (number) => { + if (typeof number === 'undefined') return number; let abs = Math.abs(number); - if(abs >= BILLION) return formatNumber(number/BILLION) + "B"; - if(abs >= MILLION) return formatNumber(number/MILLION) + "M"; - if(abs >= THOUSAND) return formatNumber(number/THOUSAND) + "K"; + if (abs >= BILLION) return formatNumber(number / BILLION) + 'B'; + if (abs >= MILLION) return formatNumber(number / MILLION) + 'M'; + if (abs >= THOUSAND) return formatNumber(number / THOUSAND) + 'K'; return formatNumber(number); - } + }, }, tables: { - currencyFormatter: formatNumber - } + currencyFormatter: formatNumber, + }, }; OCEChild.STYLING = styling; @@ -206,23 +245,14 @@ OCEChild.TRANSLATIONS = translations; CorruptionRickDashboard.STYLING = JSON.parse(JSON.stringify(styling)); -CorruptionRickDashboard.STYLING.charts.traceColors = ["#234e6d", "#3f7499", "#80b1d3", "#afd5ee", "#d9effd"]; +CorruptionRickDashboard.STYLING.charts.traceColors = ['#234e6d', '#3f7499', '#80b1d3', '#afd5ee', '#d9effd']; -class OceSwitcher extends ViewSwitcher{} +class OceSwitcher extends ViewSwitcher {} -OceSwitcher.views.default = OCEChild; -OceSwitcher.views.corruptionRiskDashboard = CorruptionRickDashboard; +OceSwitcher.views['m-and-e'] = OCEChild; +OceSwitcher.views.crd = CorruptionRickDashboard; ReactDOM.render(, document.getElementById('dg-container')); - -if("ocvn.developmentgateway.org" == location.hostname){ - (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= - function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; - e=o.createElement(i);r=o.getElementsByTagName(i)[0]; - e.src='//www.google-analytics.com/analytics.js'; - r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); - ga('create','UA-78202947-1');ga('send','pageview'); -} + translations={translations.en_US} + styling={styling} +/>, document.getElementById('dg-container')); diff --git a/ui/oce-standalone/style.less b/ui/oce-standalone/style.less index 088495d81..6027da1e3 100644 --- a/ui/oce-standalone/style.less +++ b/ui/oce-standalone/style.less @@ -210,7 +210,8 @@ aside [role=navigation]{ .content.map-content{ padding-left: 0; - margin-top: 64px; + margin-top: @headerHeight * 2; + margin-bottom: 66px; padding-right: 0; overflow: hidden; .leaflet-container{ @@ -432,3 +433,14 @@ aside [role=navigation]{ .logo-wrapper>*{ float:left; } + +.language-switcher{ + display: flex; + justify-content: center; + align-items: center; + img{ + width: 32px; + margin-left: .4em; + cursor: pointer; + } +} diff --git a/ui/oce/corruption-risk/archive.jsx b/ui/oce/corruption-risk/archive.jsx new file mode 100644 index 000000000..9f665615a --- /dev/null +++ b/ui/oce/corruption-risk/archive.jsx @@ -0,0 +1,45 @@ +import cn from 'classnames'; +import CRDPage from './page'; +import TopSearch from './top-search'; +import { wireProps } from './tools'; + +class Archive extends CRDPage { + render() { + const { className, searchQuery, doSearch, topSearchPlaceholder, translations, data, List, dataEP + , countEP, navigate } = this.props; + + const count = data.get('count'); + + return ( +
+ + + {searchQuery &&

+ { + (count === 1 ? + this.t('crd:contracts:top-search:resultsFor:sg') : + this.t('crd:contracts:top-search:resultsFor:pl') + ).replace('$#$', count).replace( + '$#$', + searchQuery.replace(/\%22/g, '') + )} +

} + + +
+ ); + } +} + +export default Archive; diff --git a/ui/oce/corruption-risk/archive/bootstrap-table-wrapper.jsx b/ui/oce/corruption-risk/archive/bootstrap-table-wrapper.jsx new file mode 100644 index 000000000..1511c3643 --- /dev/null +++ b/ui/oce/corruption-risk/archive/bootstrap-table-wrapper.jsx @@ -0,0 +1,47 @@ +export default class BootstrapTableWrapper extends React.PureComponent { + render() { + const { + columns, + data, + page, + pageSize, + onPageChange, + onSizePerPageList, + count, + containerClass, + bordered + } = this.props; + return ( + ({ text: value, value })), + onSizePerPageList, + paginationPosition: 'both', + }} + containerClass={containerClass} + > + + ) + } +} + +BootstrapTableWrapper.defaultProps = { + bordered: false +} diff --git a/ui/oce/corruption-risk/archive/state.jsx b/ui/oce/corruption-risk/archive/state.jsx new file mode 100644 index 000000000..ba9a9a5c0 --- /dev/null +++ b/ui/oce/corruption-risk/archive/state.jsx @@ -0,0 +1,29 @@ +import URI from 'urijs'; +import { Mapping } from '../../state'; +import { indicatorIdsFlat, indicatorTypesMapping, API_ROOT } from '../../state/oce-state'; +import { fetchEP } from '../../tools'; + +export class FlaggedNrMapping extends Mapping { + constructor({ filters, ...opts }) { + super({ + deps: [indicatorIdsFlat, filters, indicatorTypesMapping], + mapper: (indicatorIds, filters, indicatorTypesMapping) => Promise.all( + indicatorIds.map(indicatorId => + fetchEP( + new URI(`${API_ROOT}/flags/${indicatorId}/count`).addSearch(filters.toJS()) + ).then(data => { + if (!data[0]) return null; + return { + indicatorId, + count: data[0].count, + types: indicatorTypesMapping[indicatorId].types + } + }) + ) + ).then(data => + data.filter(datum => !!datum).sort((a, b) => b.count - a.count) + ), + ...opts, + }) + } +} diff --git a/ui/oce/corruption-risk/archive/title-below.jsx b/ui/oce/corruption-risk/archive/title-below.jsx new file mode 100644 index 000000000..e31dce08f --- /dev/null +++ b/ui/oce/corruption-risk/archive/title-below.jsx @@ -0,0 +1,16 @@ +const TitleBelow = ({ title, children, filters, ...props }) => ( +
+ {React.cloneElement( + React.Children.only(children) + , props)} +

+ +   + {title} +

+
+); + +export default TitleBelow; diff --git a/ui/oce/corruption-risk/archive/tools.jsx b/ui/oce/corruption-risk/archive/tools.jsx new file mode 100644 index 000000000..840c67415 --- /dev/null +++ b/ui/oce/corruption-risk/archive/tools.jsx @@ -0,0 +1,9 @@ +import { Label } from 'recharts'; + +export function renderTopLeftLabel({ content, ...props }) { + return ( + + + ) +} diff --git a/ui/oce/corruption-risk/backend-date-filterable.jsx b/ui/oce/corruption-risk/backend-date-filterable.jsx new file mode 100644 index 000000000..62b5f5b42 --- /dev/null +++ b/ui/oce/corruption-risk/backend-date-filterable.jsx @@ -0,0 +1,39 @@ +import { Map, Set } from 'immutable'; +import { cloneChild } from './tools'; + +class BackendDateFilterable extends React.PureComponent { + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.decoratedFilters = this.decorateFilters(this.props); + } + + decorateFilters({ filters, years, months }) { + const monthly = years.count() === 1; + return filters + .set('year', years) + .set('monthly', monthly) + .set('month', monthly && months.count() != 12 ? months : Set()) + ; + } + + componentWillUpdate(nextProps) { + if (['filters', 'years', 'months'].some(prop => this.props[prop] != nextProps[prop])) { + this.setState({ + decoratedFilters: this.decorateFilters(nextProps), + }) + } + } + + render() { + const decoratedProps = Object.assign({}, this.props, { + filters: this.state.decoratedFilters, + }); + delete decoratedProps.years; + delete decoratedProps.months; + delete decoratedProps.monthly; + return cloneChild(this, decoratedProps); + } +} + +export default BackendDateFilterable; diff --git a/ui/oce/corruption-risk/clickable-crosstab/index.jsx b/ui/oce/corruption-risk/clickable-crosstab/index.jsx new file mode 100644 index 000000000..304d17091 --- /dev/null +++ b/ui/oce/corruption-risk/clickable-crosstab/index.jsx @@ -0,0 +1,107 @@ +import cn from 'classnames'; +import Crosstab from '../crosstab'; +import styles from './style.less'; +import { colorLuminance } from '../tools'; + +class ClickableCrosstab extends Crosstab { + constructor(...args) { + super(...args); + this.state.currentlySelected = false; + this.deselect = () => this.setState({ currentlySelected: false }); + } + + row(rowData, rowIndicatorID) { + const { currentlySelected } = this.state; + const rowIndicatorName = this.t(`crd:indicators:${rowIndicatorID}:name`); + return ( + + {rowIndicatorName} + {rowData.getIn([rowIndicatorID, 'count'])} + {rowData.map((datum, indicatorID) => { + if(indicatorID === rowIndicatorID) { + return —; + } else { + const { showRawNumbers } = this.props; + const count = datum.get('count', 0); + const percent = datum.get('percent', 0); + const color = colorLuminance('#00ff00', percent / 100 - .5); + const style = { backgroundColor: color }; + const selected = rowIndicatorID === currentlySelected.rowIndicatorID && + indicatorID === currentlySelected.indicatorID; + + return ( + this.setState({ currentlySelected: { rowIndicatorID, indicatorID }})} + > + { showRawNumbers ? + `${count} (${percent.toFixed(2)} %)` : + `${percent.toFixed(2)} %`} + + ); + } + }).toArray()} + + ); + } + + maybeGetBox() { + const { currentlySelected } = this.state; + if (!currentlySelected) return null; + const { rowIndicatorID, indicatorID } = currentlySelected; + + const row = this.props.data.get(rowIndicatorID); + const datum = row.get(indicatorID); + + const rowIndicatorName = this.t(`crd:indicators:${rowIndicatorID}:name`); + const rowIndicatorDescription = this.t(`crd:indicators:${rowIndicatorID}:indicator`); + const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); + const indicatorName = this.t(`crd:indicators:${indicatorID}:name`); + const count = datum.get('count'); + const percent = datum.get('percent'); + + return ( +
+
+
+ {this.t('crd:corruptionType:crosstab:popup:percents') + .replace('$#$', percent.toFixed(2)) + .replace('$#$', rowIndicatorName) + .replace('$#$', indicatorName)} +
+
+
{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}
+

{rowIndicatorName}: {rowIndicatorDescription}

+

{indicatorName}: {indicatorDescription}

+
+
+
+ ); + } + + componentDidMount() { + super.componentDidMount(); + document.body.addEventListener('click', this.deselect); + } + + componentWillUnmount() { + super.componentWillUnmount && super.componentWillUnmount(); + document.body.removeEventListener('click', this.deselect); + } + + render() { + const { data } = this.props; + if (!data) return null; + if (!data.count()) return null; + return ( +
{ e.stopPropagation(); }} className="clickable-crosstab"> + {super.render()} + {this.maybeGetBox()} +
+ ); + } +} + +export default ClickableCrosstab; diff --git a/ui/oce/corruption-risk/clickable-crosstab/style.less b/ui/oce/corruption-risk/clickable-crosstab/style.less new file mode 100644 index 000000000..a3767382f --- /dev/null +++ b/ui/oce/corruption-risk/clickable-crosstab/style.less @@ -0,0 +1,22 @@ +.dashboard-corruption-risk .clickable-crosstab { + div.crosstab-box { + background: #f0f0f0; + font-size: .9em; + .title { + color: #144361; + font-size: 1.3em; + } + h5 { + color: green; + font-weight: bold; + margin-bottom: 5px; + } + p { + color: #999999; + margin-bottom: 0; + strong { + color: #333; + } + } + } +} \ No newline at end of file diff --git a/ui/oce/corruption-risk/constants.es6 b/ui/oce/corruption-risk/constants.es6 index da759cb06..9824ce1db 100644 --- a/ui/oce/corruption-risk/constants.es6 +++ b/ui/oce/corruption-risk/constants.es6 @@ -1,5 +1,4 @@ -export const LOGIN_URL = '/login?referrer=/ui/index.html?corruption-risk-dashboard'; +export const LOGIN_URL = '/login?referrer=/ui/index.html'; export const POPUP_HEIGHT = 150; export const POPUP_WIDTH = 400; - - +export const CORRUPTION_TYPES = ['FRAUD', 'RIGGING', 'COLLUSION']; diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx new file mode 100644 index 000000000..bd30a6f59 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -0,0 +1,150 @@ +import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; +import { List } from 'immutable'; +// eslint-disable-next-line no-unused-vars +import rbtStyles from 'react-bootstrap-table/dist/react-bootstrap-table-all.min.css'; +import CRDPage from '../page'; +import { getAwardAmount, mkContractLink, wireProps, _3LineText } from '../tools'; +import PaginatedTable from '../paginated-table'; +import Archive from '../archive'; +import BackendDateFilterable from '../backend-date-filterable'; + +class CList extends PaginatedTable { + getCustomEP() { + const { searchQuery } = this.props; + const eps = super.getCustomEP(); + return searchQuery ? + eps.map(ep => ep.addSearch('text', searchQuery)) : + eps; + } + + componentDidUpdate(prevProps, prevState) { + const propsChanged = ['filters', 'searchQuery'].some(key => this.props[key] !== prevProps[key]); + if (propsChanged) { + this.fetch(); + } else { + super.componentDidUpdate(prevProps, prevState); + } + } + + render() { + const { data, navigate } = this.props; + + if (!data) return null; + + const contracts = data.get('data', List()); + const count = data.get('count', 0); + + const { pageSize, page } = this.state; + + const jsData = contracts.map((contract) => { + const tenderAmount = contract.getIn(['tender', 'value', 'amount'], 'N/A') + + ' ' + + contract.getIn(['tender', 'value', 'currency'], ''); + + const startDate = contract.getIn(['tender', 'tenderPeriod', 'startDate']); + + const flags = contract.get('flags'); + + const flagTypes = flags.get('laggedStats', List()) + .map(flagType => this.t(`crd:corruptionType:${flagType.get('type')}:name`)) + .join(', ') || 'N/A'; + + return { + status: contract.getIn(['tender', 'status'], 'N/A'), + id: contract.get('ocid'), + title: contract.getIn(['tender', 'title'], 'N/A'), + PEName: contract.getIn(['tender', 'procuringEntity', 'name'], 'N/A'), + tenderAmount, + awardAmount: getAwardAmount(contract), + startDate: startDate ? new Date(startDate).toLocaleDateString() : 'N/A', + flagTypes, + nrFlags: flags.get('totalFlagged'), + }; + }).toJS(); + + return ( + this.setState({ page: newPage }), + sizePerPage: pageSize, + sizePerPageList: [20, 50, 100, 200].map(value => ({ text: value, value })), + onSizePerPageList: newPageSize => this.setState({ pageSize: newPageSize }), + paginationPosition: 'both', + }} + > + + {this.t('crd:contracts:baseInfo:status')} + + + + {this.t('crd:procurementsTable:contractID')} + + + + {this.t('crd:general:contract:title')} + + + + {this.t('crd:contracts:list:procuringEntity')} + + + + {this.t('crd:procurementsTable:tenderAmount')} + + + + {this.t('crd:contracts:list:awardAmount')} + + + + {this.t('crd:procurementsTable:tenderDate')} + + + + {this.t('crd:procurementsTable:flagType')} + + + + {this.t('crd:procurementsTable:noOfFlags')} + + + ); + } +} + +export default class Contracts extends CRDPage { + render() { + const { searchQuery, doSearch, navigate } = this.props; + return ( + + + + ); + } +} diff --git a/ui/oce/corruption-risk/contracts/single/donuts/index.jsx b/ui/oce/corruption-risk/contracts/single/donuts/index.jsx new file mode 100644 index 000000000..df926d2a8 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/index.jsx @@ -0,0 +1,36 @@ +import cn from 'classnames'; +import backendYearFilterable from '../../../../backend-year-filterable'; +import Chart from '../../../../visualizations/charts/index.jsx'; +import style from './style.less'; +import translatable from '../../../../translatable'; + +class CenterTextDonut extends translatable(React.PureComponent) { + getClassnames() { + return ['center-text-donut']; + } + + render() { + const { Donut } = this.constructor; + return ( +
+
+ +
+ {this.getCenterText()} +
+
+

+ {this.getTitle()} +

+
+ ); + } +} + +CenterTextDonut.Donut = class extends backendYearFilterable(Chart){}; + +export default CenterTextDonut; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx new file mode 100644 index 000000000..febd46872 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx @@ -0,0 +1,87 @@ +import CenterTextDonut from './index.jsx'; + +class NrOfContractsWithPE extends CenterTextDonut { + getClassnames() { + return super.getClassnames().concat('nr-contracts'); + } + + getCenterText() { + const { data } = this.props; + if (!data) return null; + return ( +
+ {data.get('thisPE')} +
+ {this.t('crd:contract:nrContractWithPE:of')} +   + {data.get('total')} +
+
+ ); + } + + getTitle() { + return this.t('crd:contract:nrContractWithPE:name'); + } +} + +NrOfContractsWithPE.Donut = class extends CenterTextDonut.Donut { + getCustomEP() { + const { procuringEntityId, supplierId } = this.props; + if (!procuringEntityId || !supplierId) return []; + return [ + `ocds/release/count/?procuringEntityId=${procuringEntityId}`, + `ocds/release/count/?procuringEntityId=${procuringEntityId}` + + `&supplierId=${supplierId}&awardStatus=active` + ]; + } + + transform([total, thisPE]){ + return { + thisPE, + total + } + } + + componentDidUpdate(prevProps, ...rest) { + const peChanged = this.props.procuringEntityId != prevProps.procuringEntityId; + const supplierChanged = this.props.supplierId != prevProps.supplierId; + if (peChanged || supplierChanged) { + this.fetch(); + } else { + super.componentDidUpdate(prevProps, ...rest); + } + } + + getData() { + const { contract } = this.props; + const data = super.getData(); + if (!data) return []; + return [{ + labels: [ + this.t('crd:contract:nrContractWithPE:match'), + this.t('crd:contract:nrContractWithPE:total') + ], + values: [data.get('thisPE'), data.get('total')], + hoverlabel: { + bgcolor: '#144361' + }, + hoverinfo: 'none', + textinfo: 'none', + hole: 0.8, + type: 'pie', + marker: { + colors: ['#72c47e', '#2e833a'], + }, + }]; + } + + getLayout() { + return { + showlegend: false, + paper_bgcolor: 'rgba(0,0,0,0)', + }; + } +}; + +export default NrOfContractsWithPE; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx new file mode 100644 index 000000000..a90bdd2f8 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx @@ -0,0 +1,68 @@ +import { List } from 'immutable'; +import CenterTextDonut from './index.jsx'; + +class NrOfBidders extends CenterTextDonut { + getClassnames() { + return super.getClassnames().concat('nr-of-bidders'); + } + + getCenterText() { + const { count, data: avg } = this.props; + if (isNaN(avg) || isNaN(count)) return ''; + return ( +
+ {count} + /{avg.toFixed(2)} +
+ ); + } + + getTitle() { + return this.t('crd:contract:nrBiddersVsAvg:name'); + } +} + +NrOfBidders.Donut = class extends CenterTextDonut.Donut { + transform(data) { + try { + return data[0].averageNoTenderers; + } catch(_) { + return 0; + } + } + + getData() { + const avg = super.getData(); + const { count } = this.props; + if (isNaN(avg) || isNaN(count)) return []; + return [{ + labels: [ + this.t('crd:contract:nrBiddersVsAvg:thisLabel'), + this.t('crd:contract:nrBiddersVsAvg:avgLabel') + ], + values: [count, avg], + hoverlabel: { + bgcolor: '#144361' + }, + hoverinfo: 'none', + textinfo: 'none', + hole: 0.8, + type: 'pie', + marker: { + colors: ['#289df5', '#fac329'], + }, + }]; + } + + getLayout() { + return { + showlegend: false, + paper_bgcolor: 'rgba(0,0,0,0)', + }; + } +} + +NrOfBidders.Donut.endpoint = 'averageNumberOfTenderers'; +NrOfBidders.Donut.UPDATABLE_FIELDS = CenterTextDonut.Donut.UPDATABLE_FIELDS.concat('count'); + +export default NrOfBidders; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/index.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/index.jsx new file mode 100644 index 000000000..5bae61fc8 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/index.jsx @@ -0,0 +1,86 @@ +import CenterTextDonut from '../index.jsx'; + +class PercentPESpending extends CenterTextDonut { + getCenterText() { + const { data } = this.props; + if (!data) return null; + return ( + +   + {data.get('percentage').toFixed(2)} + % + + ); + } + + getTitle() { + return this.t('crd:contract:percentPEspending:name'); + } +} + +PercentPESpending.Donut = class extends CenterTextDonut.Donut { + getCustomEP() { + const { procuringEntityId, supplierId } = this.props; + return `percentageAmountAwarded?procuringEntityId=${procuringEntityId}&supplierId=${supplierId}`; + } + + transform(data) { + try { + const { percentage, totalAwarded, totalAwardedToSuppliers } = data[0]; + return { + percentage, + total: totalAwarded.sum, + toSuppliers: totalAwardedToSuppliers.sum, + }; + } catch (e) { + return { + percentage: 0, + total: 0, + toSuppliers: 0, + }; + } + } + + componentDidUpdate(prevProps, ...rest) { + const peChanged = this.props.procuringEntityId !== prevProps.procuringEntityId; + const supplierChanged = this.props.supplierId !== prevProps.supplierId; + if (peChanged || supplierChanged) { + this.fetch(); + } else { + super.componentDidUpdate(prevProps, ...rest); + } + } + + getData() { + const data = super.getData(); + if (!data || !data.count()) return []; + const toSuppliers = data.get('toSuppliers'); + const total = data.get('total'); + return [{ + labels: [ + this.t('crd:contract:percentPEspending:this'), + this.t('crd:contract:percentPEspending:match'), + ], + values: [toSuppliers, total-toSuppliers], + hoverlabel: { + bgcolor: '#144361', + }, + hoverinfo: 'none', + textinfo: 'none', + hole: 0.8, + type: 'pie', + marker: { + colors: ['#40557d', '#289df5'], + }, + }]; + } + + getLayout() { + return { + showlegend: false, + paper_bgcolor: 'rgba(0,0,0,0)', + }; + } +}; + +export default PercentPESpending; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/popup.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/popup.jsx new file mode 100644 index 000000000..d002fb86b --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/popup.jsx @@ -0,0 +1,35 @@ +// eslint-disable-next-line no-unused-vars +const POPUP_WIDTH = 300; +const POPUP_HEIGHT = 90; + +class PercentPEPopup extends React.Component { + render() { + const { x, y, points, data } = this.props; + const total = data.get('total'); + const { v, label } = points[0]; + const left = x - (POPUP_WIDTH / 2) + 30; + const top = y - POPUP_HEIGHT - 12; + const value = v / total * 100; + const formattedValue = Math.round(value) === value ? + value : + value.toFixed(2); + + return ( +
+ {label.replace(/\$#\$/g, formattedValue)} +
+
+ ); + } +} + +export default PercentPEPopup; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/style.less b/ui/oce/corruption-risk/contracts/single/donuts/style.less new file mode 100644 index 000000000..26273cb8f --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/style.less @@ -0,0 +1,37 @@ +.center-text-donut { + text-align: center; + &>div{ + position: relative; + } + + &>div, .center-text { + display: flex; + align-items: center; + justify-content: center; + } + + .center-text { + font-size: 3vw; + font-weight: bold; + color: #144361; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: -1; + } + + &.nr-of-bidders{ + .secondary { + color: #c0c0c0; + } + } + + &.nr-contracts{ + .secondary { + font-size: .3em; + margin-top: -1em; + } + } +} diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx new file mode 100644 index 000000000..5f566b8de --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -0,0 +1,353 @@ +import { Map, List, Set } from 'immutable'; +import CRDPage from '../../page'; +import Visualization from '../../../visualization'; +import translatable from '../../../translatable'; +import TopSearch from '../../top-search'; +import NrOfBidders from './donuts/nr-of-bidders'; +import NrOfContractsWithThisPE from './donuts/nr-contract-with-pe'; +import PercentPESpending from './donuts/percent-pe-spending'; +import PercentPESpendingPopup from './donuts/percent-pe-spending/popup'; +import Crosstab from '../../clickable-crosstab'; +import CustomPopup from '../../custom-popup'; +import DonutPopup from '../../donut/popup'; +import { wireProps } from '../../tools'; +// eslint-disable-next-line no-unused-vars +import styles from '../style.less'; +import DataFetcher from '../../data-fetcher'; + +class CrosstabExplanation extends translatable(React.PureComponent) { + render() { + const { data, totalContracts, nrFlags, corruptionType } = this.props; + const template = nrFlags === 1 ? + this.t('crd:contracts:crosstab:explanation:sg') : + this.t('crd:contracts:crosstab:explanation:pl'); + + return ( +

+ {template.replace('$#$', data) + .replace('$#$', (data / totalContracts * 100).toFixed(2)) + .replace('$#$', nrFlags) + .replace( + '$#$', + this.t(`crd:corruptionType:${corruptionType}:name`) + .toLowerCase() + )} +

+ ); + } +}; + +class Info extends translatable(Visualization) { + getCustomEP() { + const { id } = this.props; + return `flaggedRelease/ocid/${id}`; + } + + render() { + const { data, supplier, gotoSupplier } = this.props; + + const title = data.getIn(['tender', 'title']); + const startDate = data.getIn(['tender', 'tenderPeriod', 'startDate']); + const endDate = data.getIn(['tender', 'tenderPeriod', 'endDate']); + const award = data.get('awards', List()).find(a => + a.get('status') === 'active') || Map(); + + const PE = data.getIn(['tender', 'procuringEntity']); + + const flagCount = data.get('flags', List()).filter(flag => flag.get && flag.get('value')).count(); + return ( +
+
+
+
{this.t('crd:procurementsTable:contractID')}
+
{data.get('ocid')}
+
+
+
{this.t('crd:contracts:baseInfo:status')}
+
{data.get('tag', []).join(', ')}
+
+
+ Flag icon +   + {flagCount} +   + {this.t(flagCount === 1 ? + 'crd:contracts:baseInfo:flag:sg' : + 'crd:contracts:baseInfo:flag:pl')} +
+
+ {title && +
+ {title &&
{this.t('crd:general:contract:title')}
} + {title &&
{title}
} +
+ } + + + + + + + + +
+ {PE &&
+
{this.t('crd:contracts:baseInfo:procuringEntityName')}
+
+ + {PE.get('name')} + +
+
} +
+
+
{this.t('crd:contracts:baseInfo:buyer')}
+
{data.getIn(['buyer', 'name'], this.t('general:undefined'))}
+
+
+
+
{this.t('crd:contracts:baseInfo:suppliers')}
+
+ {supplier ? + + {supplier.get('name')} + : + this.t('general:undefined') + } +
+
+
+ + + + + + + + + + + + + + + +
+ {this.t('crd:contracts:baseInfo:tenderAmount')} +   + + {data.getIn(['tender', 'value', 'amount'], this.t('general:undefined'))} +   + {data.getIn(['tender', 'value', 'currency'])} + + + {this.t('crd:contracts:baseInfo:tenderDates')} +   + + {startDate ? + + {new Date(startDate).toLocaleDateString()} + – + : + this.t('general:undefined')} + + {endDate && new Date(endDate).toLocaleDateString()} + +
+ {this.t('crd:contracts:list:awardAmount')} +   + + {award.getIn(['value', 'amount'], this.t('general:undefined'))} +   + {award.getIn(['value', 'currency'])} + + + {this.t('crd:contracts:baseInfo:awardDate')} +   + + {award.has('date') ? + new Date(award.get('date')).toLocaleDateString() : + this.t('general:undefined')} + +
{this.t('crd:contracts:baseInfo:contractAmount')}{this.t('crd:contracts:baseInfo:contractDates')}
+
+ ); + } +} + +export default class Contract extends CRDPage { + constructor(...args) { + super(...args); + this.state = { + contract: Map(), + crosstab: Map(), + indicators: {}, + }; + } + + groupIndicators({ indicatorTypesMapping }, { contract }) { + if (!indicatorTypesMapping || !Object.keys(indicatorTypesMapping).length || !contract) return; + const newIndicators = {}; + contract.get('flags', List()).forEach((flag, name) => { + if (flag.get && flag.get('value')) { + indicatorTypesMapping[name].types.forEach((type) => { + newIndicators[type] = newIndicators[type] || []; + newIndicators[type].push(name); + }); + } + }); + this.setState({ indicators: newIndicators }); + } + + componentDidMount() { + super.componentDidMount(); + this.groupIndicators(this.props, this.state); + } + + componentWillUpdate(nextProps, nextState) { + const indicatorsChanged = this.props.indicatorTypesMapping !== nextProps.indicatorTypesMapping; + const contractChanged = this.state.contract !== nextState.contract; + if (indicatorsChanged || contractChanged) { + this.groupIndicators(nextProps, nextState); + } + } + + maybeGetFlagAnalysis() { + const { filters, translations, years, totalContracts } = this.props; + const { indicators, crosstab } = this.state; + const noIndicators = Object.keys(indicators) + .every(corruptionType => + !indicators[corruptionType] || !indicators[corruptionType].length); + + if (noIndicators) return ( +
+

{this.t('crd:contracts:flagAnalysis')}

+

{this.t('crd:contracts:noFlags')}

+
+ ); + + return ( +
+

+ {this.t('crd:contracts:flagAnalysis')} +   + ({this.t('crd:contracts:clickCrosstabHint')}) +

+ {Object.keys(indicators).map(corruptionType => { + const nrFlags = indicators[corruptionType].length; + return ( +
+

+ {this.t(`crd:corruptionType:${corruptionType}:pageTitle`)} +

+ + + + { + const { crosstab } = this.state; + this.setState({ crosstab: crosstab.set(corruptionType, data)}) + }} + /> +
+ )})} +
+ ); + } + + render() { + const { contract } = this.state; + + const { id, translations, doSearch, filters, width, gotoSupplier } = this.props; + + if (!contract) return null; + + const supplier = contract.get('awards', List()) + .find(award => award.get('status') === 'active', undefined, Map()) + .getIn(['suppliers', 0]); + + const procuringEntityId = contract.getIn(['tender', 'procuringEntity', 'id']) || + contract.getIn(['tender', 'procuringEntity', 'identifier', 'id']); + + const donutSize = width / 3 - 100; + + return ( +
+ + this.setState({ contract })} + translations={translations} + gotoSupplier={gotoSupplier} + /> + +
+

+ {this.t('crd:contracts:contractStatistics')} +

+
+ +
+
+ {procuringEntityId && supplier && + + } +
+
+ {procuringEntityId && supplier && + + } +
+
+ {this.maybeGetFlagAnalysis()} +
+ ); + } +} + diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less new file mode 100644 index 000000000..bf302ef18 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/style.less @@ -0,0 +1,55 @@ +.contracts-page{ + margin-bottom: 40px; + table { + th, td{ + @width: 12.5%; + @ocidDateDelta: 3%; + width: @width; + &.ocid { + width: @width + @ocidDateDelta; + } + &.date { + width: @width - @ocidDateDelta; + } + } + th{ + color: #a1adbc; + } + td{ + font-weight: 600; + font-size: .9em; + } + } +} + +.react-bs-table table td, .react-bs-table table th{ + white-space: normal; +} + +.contract-page { + .flag-analysis { + clear: both; + + h2 { + padding-top: 50px; + } + } + + section.contract-statistics { + h2 { + margin-bottom: 40px; + @media screen and (max-width: 1500px) { + margin-bottom: 20px; + } + + @media screen and (max-width: 1400px) { + margin-bottom: 10px; + } + + @media screen and (max-width: 1300px) { + margin-bottom: 0; + } + + } + } +} diff --git a/ui/oce/corruption-risk/corruption-type.jsx b/ui/oce/corruption-risk/corruption-type.jsx index 557433b79..b0e2186d0 100644 --- a/ui/oce/corruption-risk/corruption-type.jsx +++ b/ui/oce/corruption-risk/corruption-type.jsx @@ -2,10 +2,10 @@ import URI from "urijs"; import {Map} from "immutable"; import {pluckImm} from "../tools"; import CustomPopupChart from "./custom-popup-chart"; -import Table from "../visualizations/tables/index"; import translatable from '../translatable'; import CRDPage from "./page"; -import { colorLuminance } from './tools'; +import { colorLuminance, sortByField } from './tools'; +import Crosstab from './crosstab'; class IndicatorTile extends CustomPopupChart{ getCustomEP(){ @@ -15,9 +15,11 @@ class IndicatorTile extends CustomPopupChart{ getData(){ const color = this.props.styling.charts.traceColors[2]; - const data = super.getData(); + let data = super.getData(); if(!data) return []; - const {monthly} = this.props; + const { monthly } = this.props; + data = data.sort(sortByField(monthly ? 'month': 'year')); + let dates = monthly ? data.map(datum => { const month = datum.get('month'); @@ -71,7 +73,7 @@ class IndicatorTile extends CustomPopupChart{ } getPopup(){ - const {indicator, monthly} = this.props; + const {monthly} = this.props; const {popup} = this.state; const {year} = popup; const data = super.getData(); @@ -85,6 +87,7 @@ class IndicatorTile extends CustomPopupChart{ } else { datum = data.find(datum => datum.get('year') == year); } + if (!datum) return null; return (
@@ -94,13 +97,13 @@ class IndicatorTile extends CustomPopupChart{

-
Procurements Flagged
+
{this.t('crd:corruptionType:indicatorTile:procurementsFlagged')}
{datum.get('totalTrue')}
-
Eligible Procurements
+
{this.t('crd:corruptionType:indicatorTile:eligibleProcurements')}
{datum.get('totalPrecondMet')}
-
% Eligible Procurements Flagged
+
{this.t('crd:corruptionType:indicatorTile:percentEligibleFlagged')}
{datum.get('percentTruePrecondMet').toFixed(2)} %
-
% Procurements Eligible
+
{this.t('crd:corruptionType:indicatorTile:percentProcurementsEligible')}
{datum.get('percentPrecondMet').toFixed(2)} %
@@ -109,109 +112,6 @@ class IndicatorTile extends CustomPopupChart{ } } -class Crosstab extends Table{ - getCustomEP(){ - const {indicators} = this.props; - return indicators.map(indicator => `flags/${indicator}/crosstab`); - } - - componentDidUpdate(prevProps, ...args){ - if(this.props.indicators != prevProps.indicators){ - this.fetch(); - } - super.componentDidUpdate(prevProps, ...args); - } - - transform(data){ - const {indicators} = this.props; - let matrix = {}, x = 0, y = 0; - for(x = 0; x - {rowIndicatorName} - {rowData.getIn([rowIndicatorID, 'count'])} - {rowData.map((datum, indicatorID) => { - const indicatorName = this.t(`crd:indicators:${indicatorID}:name`); - const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); - if(indicatorID == rowIndicatorID){ - return — - } else { - const percent = datum.get('percent'); - const count = datum.get('count'); - const color = Math.round(255 - 255 * (percent/100)) - const style = {backgroundColor: `rgb(${color}, 255, ${color})`} - return ( - - {percent && percent.toFixed(2)} % -
-
-
- {percent.toFixed(2)}% of procurements flagged for "{rowIndicatorName}" are also flagged for "{indicatorName}" -
-
-
-
-
-

{count} Procurements flagged with both;

-

{rowIndicatorName}: {rowIndicatorDescription}

-

and

-

{indicatorName}: {indicatorDescription}

-
-
-
-
- - ) - } - }).toArray()} - - ) - } - - render(){ - const {indicators, data} = this.props; - if(!data) return null; - if(!data.count()) return null; - return ( - - - - - - {data.map((_, indicatorID) => ).toArray()} - - - - {data.map((rowData, indicatorID) => this.row(rowData, indicatorID)).toArray()} - -
# Flags{this.t(`crd:indicators:${indicatorID}:name`)}
- ) - } -} function groupBy3(arr){ if(arr.length == 0) return []; @@ -254,7 +154,7 @@ class CorruptionType extends translatable(CRDPage){
{row.map(indicator => { const indicatorName = this.t(`crd:indicators:${indicator}:name`); - const indicatorDescription = this.t(`crd:indicators:${indicator}:indicator`); + const indicatorDescription = this.t(`crd:indicators:${indicator}:shortDescription`); return (
onGotoIndicator(indicator)}>
@@ -271,7 +171,7 @@ class CorruptionType extends translatable(CRDPage){ years={years} monthly={monthly} months={months} - width={width/3-60} + width={width/3-95} styling={styling} />
diff --git a/ui/oce/corruption-risk/crosstab.jsx b/ui/oce/corruption-risk/crosstab.jsx new file mode 100644 index 000000000..4892362d8 --- /dev/null +++ b/ui/oce/corruption-risk/crosstab.jsx @@ -0,0 +1,113 @@ +import cn from 'classnames'; +import Table from '../visualizations/tables/index'; + +class Crosstab extends Table { + getCustomEP() { + const { indicators } = this.props; + return indicators.map(indicator => `flags/${indicator}/crosstab`); + } + + componentDidUpdate(prevProps, ...args) { + if (this.props.indicators !== prevProps.indicators) { + this.fetch(); + } + super.componentDidUpdate(prevProps, ...args); + } + + transform(data) { + const { indicators } = this.props; + const matrix = {}; + let y = 0; + for (let x = 0; x < indicators.length; x++) { + const xIndicatorID = indicators[x]; + matrix[xIndicatorID] = {}; + const datum = data[x][0]; + for (y = 0; y < indicators.length; y++) { + const yIndicatorID = indicators[y]; + if (datum) { + matrix[xIndicatorID][yIndicatorID] = { + count: datum[yIndicatorID], + percent: datum.percent[yIndicatorID], + }; + } else { + matrix[xIndicatorID][yIndicatorID] = { + count: 0, + percent: 0, + }; + } + } + } + return matrix; + } + + row(rowData, rowIndicatorID){ + const rowIndicatorName = this.t(`crd:indicators:${rowIndicatorID}:name`); + const rowIndicatorDescription = this.t(`crd:indicators:${rowIndicatorID}:indicator`); + const lastKey = rowData.lastKeyOf(rowData.last()); + return ( + + {rowIndicatorName} + {rowData.getIn([rowIndicatorID, 'count'])} + {rowData.map((datum, indicatorID) => { + const indicatorName = this.t(`crd:indicators:${indicatorID}:name`); + const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); + if (indicatorID === rowIndicatorID) { + return —; + } + const percent = datum.get('percent'); + const count = datum.get('count'); + const color = Math.round(255 - 255 * (percent / 100)); + const style = { backgroundColor: `rgb(${color}, 255, ${color})` }; + const isLast = indicatorID === lastKey; + return ( + + {percent && percent.toFixed(2)} % +
+
+
+ {this.t('crd:corruptionType:crosstab:popup:percents') + .replace('$#$', percent.toFixed(2)) + .replace('$#$', rowIndicatorName) + .replace('$#$', indicatorName)} +
+
+
+
+
+

{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

+

{rowIndicatorName}: {rowIndicatorDescription}

+

{this.t('crd:corruptionType:crosstab:popup:and')}

+

{indicatorName}: {indicatorDescription}

+
+
+
+
+ + ); + }).toArray()} + + ) + } + + render() { + const { data } = this.props; + if (!data) return null; + if (!data.count()) return null; + return ( + + + + + + {data.map((_, indicatorID) => ).toArray()} + + + + {data.map((rowData, indicatorID) => this.row(rowData, indicatorID)).toArray()} + +
# Flags{this.t(`crd:indicators:${indicatorID}:name`)}
+ ); + } +} + +export default Crosstab; diff --git a/ui/oce/corruption-risk/custom-popup-chart.jsx b/ui/oce/corruption-risk/custom-popup-chart.jsx index 6e0dcc685..10f47c604 100644 --- a/ui/oce/corruption-risk/custom-popup-chart.jsx +++ b/ui/oce/corruption-risk/custom-popup-chart.jsx @@ -1,46 +1,53 @@ -import Chart from "../visualizations/charts/frontend-date-filterable"; -import ReactIgnore from "../react-ignore.jsx"; -import cn from "classnames"; -import {POPUP_HEIGHT, POPUP_WIDTH} from "./constants"; +import cn from 'classnames'; +import Chart from '../visualizations/charts/frontend-date-filterable'; +import ReactIgnore from '../react-ignore.jsx'; +import { POPUP_HEIGHT, POPUP_WIDTH } from './constants'; -class CustomPopupChart extends Chart{ - constructor(...args){ +class CustomPopupChart extends Chart { + constructor(...args) { super(...args); this.state = { popup: { show: false, left: 0, - top: 0 - } - } + top: 0, + }, + }; } - componentDidMount(){ + componentDidMount() { super.componentDidMount(); - const {chartContainer} = this.refs; + const { chartContainer } = this; chartContainer.on('plotly_hover', this.showPopup.bind(this)); - chartContainer.on('plotly_unhover', data => this.hidePopup()); + chartContainer.on('plotly_unhover', () => this.hidePopup()); + } + + getPopupWidth() { + return POPUP_WIDTH; } - showPopup(data){ + showPopup(data) { const point = data.points[0]; const year = point.x; const traceName = point.data.name; + const traceIndex = point.fullData.index; const POPUP_ARROW_SIZE = 8; + const popupWidth = this.getPopupWidth(); - const {xaxis, yaxis} = point; - const markerLeft = xaxis.l2p(point.pointNumber) + xaxis._offset; + const { xaxis, yaxis } = point; + const markerLeft = xaxis.l2p(xaxis._categories.indexOf(point.x)) + xaxis._offset; const markerTop = yaxis.l2p(point.y) + yaxis._offset; - const {left: parentLeft} = this.refs.chartContainer.getBoundingClientRect(); - const toTheLeft = (markerLeft + parentLeft + POPUP_WIDTH) >= window.innerWidth; - let top, left; + const { left: parentLeft } = this.chartContainer.getBoundingClientRect(); + const toTheLeft = (markerLeft + parentLeft + popupWidth) >= window.innerWidth; + let top; + let left; - if(toTheLeft){ - top = markerTop - POPUP_HEIGHT / 2; - left = markerLeft - POPUP_WIDTH - POPUP_ARROW_SIZE * 1.5; + if (toTheLeft) { + top = markerTop - (POPUP_HEIGHT / 2); + left = markerLeft - popupWidth - (POPUP_ARROW_SIZE * 1.5); } else { - top = markerTop - POPUP_HEIGHT - POPUP_ARROW_SIZE * 1.5; - left = markerLeft - POPUP_WIDTH / 2; + top = markerTop - POPUP_HEIGHT - (POPUP_ARROW_SIZE * 1.5); + left = markerLeft - (popupWidth / 2); } this.setState({ @@ -50,37 +57,38 @@ class CustomPopupChart extends Chart{ top, left, year, - traceName - } + traceName, + traceIndex, + }, }); } - hidePopup(){ + hidePopup() { this.setState({ popup: { - show: false - } + show: false, + }, }); } - render(){ - const {loading, popup} = this.state; - let hasNoData = !loading && this.hasNoData(); + render() { + const { loading, popup } = this.state; + const hasNoData = !loading && this.hasNoData(); return ( -
- {hasNoData &&
{this.t('charts:general:noData')}
} - {loading &&
- Loading...
- -
} +
+ {hasNoData &&
{this.t('charts:general:noData')}
} + {loading &&
+ Loading...
+ +
} - {popup.show && this.getPopup()} + {popup.show && this.getPopup()} - -
- + +
{ this.chartContainer = c; }} /> +
- ) + ); } } diff --git a/ui/oce/corruption-risk/custom-popup.jsx b/ui/oce/corruption-risk/custom-popup.jsx new file mode 100644 index 000000000..4a44ace7d --- /dev/null +++ b/ui/oce/corruption-risk/custom-popup.jsx @@ -0,0 +1,56 @@ +import ReactDOM from 'react-dom'; + +class CustomPopup extends React.Component { + constructor(...args) { + super(...args); + this.state = this.state || {}; + this.state = { + show: false, + x: 0, + y: 0, + }; + } + + componentDidMount() { + super.componentDidMount && super.componentDidMount(); + const chartContainer = ReactDOM.findDOMNode(this) + .querySelector('.js-plotly-plot'); + + chartContainer.on('plotly_hover', e => this.showPopup(e)); + chartContainer.on('plotly_unhover', () => this.setState({ show: false })); + chartContainer.addEventListener('mousemove', this.movePopup.bind(this)); + } + + showPopup({ points }) { + this.setState({ + show: true, + points, + }); + } + + movePopup({ offsetX: x, offsetY: y }) { + this.setState({ x, y }); + } + + render() { + const { Popup, Chart } = this.props; + const { show, x, y, points } = this.state; + return ( +
+ {show && + + } + +
+ ); + } +} + +export default CustomPopup; diff --git a/ui/oce/corruption-risk/data-fetcher.jsx b/ui/oce/corruption-risk/data-fetcher.jsx new file mode 100644 index 000000000..de2443533 --- /dev/null +++ b/ui/oce/corruption-risk/data-fetcher.jsx @@ -0,0 +1,59 @@ +import URI from 'urijs'; +import { Map } from 'immutable'; +import { cloneChild } from './tools'; +import { callFunc } from '../tools'; + +const API_ROOT = '/api'; + +const fetchEP = url => fetch(url.clone().query(''), { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + credentials: 'same-origin', + body: url.query(), +}).then(callFunc('json')); + +class DataFetcher extends React.PureComponent { + fetch() { + const { filters, endpoint, endpoints, requestNewData } = this.props; + if (endpoint) { + const uri = new URI(`${API_ROOT}/${endpoint}`).addSearch(filters.toJS()); + fetchEP(uri).then(requestNewData.bind(null, [])); + } else if (endpoints) { + Promise.all( + endpoints.map(endpoint => + fetchEP( + new URI(`${API_ROOT}/${endpoint}`).addSearch(filters.toJS()) + ) + ) + ).then(requestNewData.bind(null, [])); + } + } + + componentDidMount() { + this.fetch(); + } + + componentDidUpdate(prevProps) { + if (['filters', 'endpoint', 'endpoints'].some(prop => this.props[prop] != prevProps[prop])) { + this.props.requestNewData([], null); + this.fetch(); + } + } + + render() { + const { data } = this.props; + if (!data) return null; + return cloneChild(this, { + data + }); + } +} + +DataFetcher.defaultProps = { + filters: Map(), + requestNewData: () => null, +} + +export default DataFetcher; diff --git a/ui/oce/corruption-risk/donut/index.jsx b/ui/oce/corruption-risk/donut/index.jsx new file mode 100644 index 000000000..caaf15584 --- /dev/null +++ b/ui/oce/corruption-risk/donut/index.jsx @@ -0,0 +1,84 @@ +import cn from 'classnames'; +import backendYearFilterable from '../../backend-year-filterable'; +import Chart from '../../visualizations/charts'; +import { wireProps } from '../tools'; +import { pluck } from '../../../tools'; +import CustomPopup from '../custom-popup'; +import DonutPopup from './popup'; + +class Donut extends backendYearFilterable(Chart) { + getCustomEP() { + return this.props.endpoint || 'ocds/release/count'; + } + + hasNoData() { + return this.props.data && !this.props.data.length; + } + + getData() { + const { data } = this.props; + if (!data.length) return []; + return [{ + labels: data.map(pluck('label')), + values: data.map(pluck('value')), + hoverlabel: { + bgcolor: '#144361', + }, + hoverinfo: 'none', + textinfo: 'none', + hole: 0.8, + type: 'pie', + marker: { + colors: data.map(pluck('color')), + }, + }]; + } + + getLayout() { + return { + showlegend: false, + paper_bgcolor: 'rgba(0,0,0,0)', + }; + } +} + +class DonutWrapper extends React.PureComponent { + render() { + const { title, subtitle, className, data, CenterText, styling, translations } = this.props; + return ( +
+
+ + +
+

+ {title} + {subtitle && [
, {subtitle}]} +

+
+ ); + } +} + +class CenterTextDonut extends React.Component { + render() { + return ( + + ); + } +} + +export default CenterTextDonut; diff --git a/ui/oce/corruption-risk/donut/popup/index.jsx b/ui/oce/corruption-risk/donut/popup/index.jsx new file mode 100644 index 000000000..dcaa4f27d --- /dev/null +++ b/ui/oce/corruption-risk/donut/popup/index.jsx @@ -0,0 +1,43 @@ +// eslint-disable-next-line no-unused-vars +import style from './style.less'; + +const POPUP_WIDTH = 300; + +class DonutPopup extends React.Component { + render() { + const { x, y, points } = this.props; + const { v: value, label } = points[0]; + const formattedValue = Math.round(value) === value ? + value : + value.toFixed(2); + + const text = label.replace(/\$#\$/g, formattedValue); + + let POPUP_HEIGHT = 55; + + + if (text.length >= 40) POPUP_HEIGHT = 75; + if (text.length >= 65) POPUP_HEIGHT = 90; + + const left = x - (POPUP_WIDTH / 2) + 30; + const top = y - POPUP_HEIGHT - 12; + + return ( +
+ {text} +
+
+ ); + } +} + +export default DonutPopup; diff --git a/ui/oce/corruption-risk/donut/popup/style.less b/ui/oce/corruption-risk/donut/popup/style.less new file mode 100644 index 000000000..057d4a219 --- /dev/null +++ b/ui/oce/corruption-risk/donut/popup/style.less @@ -0,0 +1,7 @@ +.dashboard-corruption-risk .content { + .crd-popup.donut-popup { + color: #fefefe; + font-weight: bold; + padding: 15px; + } +} diff --git a/ui/oce/corruption-risk/filters/box.jsx b/ui/oce/corruption-risk/filters/box.jsx index 6ab4d4fbd..e30de05c2 100644 --- a/ui/oce/corruption-risk/filters/box.jsx +++ b/ui/oce/corruption-risk/filters/box.jsx @@ -22,9 +22,9 @@ class FilterBox extends FilterTab{
e.stopPropagation()}> {this.getBox()}
- +   - +
} diff --git a/ui/oce/corruption-risk/filters/date.jsx b/ui/oce/corruption-risk/filters/date.jsx index 93a3e03cc..dbbc1fae1 100644 --- a/ui/oce/corruption-risk/filters/date.jsx +++ b/ui/oce/corruption-risk/filters/date.jsx @@ -11,7 +11,7 @@ class DateBox extends FilterBox{ } getTitle(){ - return 'Date'; + return this.t('filters:tabs:date:title'); } reset(){ @@ -49,17 +49,16 @@ class DateBox extends FilterBox{ return ( {year} ) - })} + })}

- To select a single year and be able to select months, - hold 'shift' while clicking on a year. + {this.t('yearsBar:ctrlClickHint')}

{selectedYears.count() == 1 && months.map(month => { @@ -73,14 +72,14 @@ class DateBox extends FilterBox{ } return ( + key={month} + className={cn('toggleable-item', {selected})} + onClick={toggleMonth} + > {this.t(`general:months:${month}`)} ) - })} + })}
) diff --git a/ui/oce/corruption-risk/filters/index.jsx b/ui/oce/corruption-risk/filters/index.jsx index 535af82d5..56f7b832c 100644 --- a/ui/oce/corruption-risk/filters/index.jsx +++ b/ui/oce/corruption-risk/filters/index.jsx @@ -5,40 +5,38 @@ import ProcurementMethodBox from "./procurement-method"; import ValueAmount from "./value-amount"; import DateBox from "./date"; import {fetchJson, range, pluck} from "../../tools"; +import translatable from '../../translatable'; -class Filters extends React.Component{ +class Filters extends translatable(React.Component) { render(){ const {onUpdate, translations, currentBoxIndex, requestNewBox, state, allYears, allMonths, onApply, appliedFilters} = this.props; const {BOXES} = this.constructor; return (
e.stopPropagation()}> -
-
-
-
Filter your data
+
+
{this.t('filters:hint')}
{BOXES.map((Box, index) => { return ( requestNewBox(currentBoxIndex === index ? null : index)} - state={state} - onUpdate={(slug, newState) => onUpdate(state.set(slug, newState))} - translations={translations} - onApply={newState => onApply(newState)} - allYears={allYears} - allMonths={allMonths} - appliedFilters={appliedFilters} + key={index} + open={currentBoxIndex === index} + onClick={e => requestNewBox(currentBoxIndex === index ? null : index)} + state={state} + onUpdate={(slug, newState) => onUpdate(state.set(slug, newState))} + translations={translations} + onApply={newState => onApply(newState)} + allYears={allYears} + allMonths={allMonths} + appliedFilters={appliedFilters} /> ) - })} + })}
-
+
-
) } diff --git a/ui/oce/corruption-risk/filters/organizations.jsx b/ui/oce/corruption-risk/filters/organizations.jsx index ac4eeb39a..561d10a31 100644 --- a/ui/oce/corruption-risk/filters/organizations.jsx +++ b/ui/oce/corruption-risk/filters/organizations.jsx @@ -16,7 +16,7 @@ class Organizations extends FilterBox{ } getTitle(){ - return 'Organizations'; + return this.t('filters:tabs:organizations:title'); } getBox(){ diff --git a/ui/oce/corruption-risk/filters/procurement-method.jsx b/ui/oce/corruption-risk/filters/procurement-method.jsx index eaf0e286a..0cd220454 100644 --- a/ui/oce/corruption-risk/filters/procurement-method.jsx +++ b/ui/oce/corruption-risk/filters/procurement-method.jsx @@ -14,7 +14,7 @@ class ProcurementMethodBox extends FilterBox{ } getTitle(){ - return 'Procurement Method'; + return this.t('filters:tabs:procurementMethod:title'); } getBox(){ diff --git a/ui/oce/corruption-risk/filters/value-amount.jsx b/ui/oce/corruption-risk/filters/value-amount.jsx index 8096377dd..4ffc626b9 100644 --- a/ui/oce/corruption-risk/filters/value-amount.jsx +++ b/ui/oce/corruption-risk/filters/value-amount.jsx @@ -42,7 +42,7 @@ class ValueAmount extends FilterBox { } getTitle() { - return 'Value amount'; + return this.t('filters:tabs:valueAmount:title'); } update(slug, {min, max}, {min: minPossibleValue, max: maxPossibleValue}) { diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 33a1015d6..5ac5d222e 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -1,34 +1,35 @@ -import style from "./style.less"; -import cn from "classnames"; -import URI from "urijs"; -import {fetchJson, debounce, cacheFn, range, pluck, callFunc} from "../tools"; -import OverviewPage from "./overview-page"; -import CorruptionTypePage from "./corruption-type"; -import {Map, Set} from "immutable"; -import IndividualIndicatorPage from "./individual-indicator"; -import Filters from "./filters"; -import TotalFlags from "./total-flags"; -import LandingPopup from "./landing-popup"; -import {LOGIN_URL} from "./constants"; +import cn from 'classnames'; +import URI from 'urijs'; +import { Map, Set } from 'immutable'; +import PropTypes from 'prop-types'; +import { fetchJson, debounce, cacheFn, range, pluck, callFunc } from '../tools'; +import OverviewPage from './overview-page'; +import CorruptionTypePage from './corruption-type'; +import IndividualIndicatorPage from './individual-indicator'; +import ContractsPage from './contracts'; +import ContractPage from './contracts/single'; +import SuppliersPage from './suppliers'; +import SupplierPage from './suppliers/single'; +import ProcuringEntitiesPage from './procuring-entities'; +import ProcuringEntityPage from './procuring-entities/single'; +import Filters from './filters'; +import LandingPopup from './landing-popup'; +import { LOGIN_URL } from './constants'; +// eslint-disable-next-line no-unused-vars +import style from './style.less'; +import Sidebar from './sidebar'; +import { filters as CRDFilters } from '../state/oce-state'; -const ROLE_ADMIN = 'ROLE_ADMIN'; - -const CORRUPTION_TYPES = { - FRAUD: "Fraud", - RIGGING: "Process rigging", - COLLUSION: "Collusion" -}; - -class CorruptionRiskDashboard extends React.Component{ - constructor(...args){ +// eslint-disable-next-line no-undef +class CorruptionRiskDashboard extends React.Component { + constructor(...args) { super(...args); - this.state={ + this.state = { dashboardSwitcherOpen: false, user: { loggedIn: false, - isAdmin: false + isAdmin: false, }, - page: 'overview', indicatorTypesMapping: {}, currentFiltersState: Map(), appliedFilters: Map(), @@ -37,238 +38,363 @@ class CorruptionRiskDashboard extends React.Component{ allYears: [], width: 0, data: Map(), - showLandingPopup: !localStorage.alreadyVisited + showLandingPopup: !localStorage.alreadyVisited, }; - localStorage.alreadyVisited = true; - - this.destructFilters = cacheFn(filters => { - return { - filters: filters.delete('years').delete('months'), - years: filters.get('years', Set()), - months: filters.get('months', Set()) - } - }); - } - - fetchUserInfo(){ - const noCacheUrl = new URI('/isAuthenticated').addSearch('time', Date.now()); - fetchJson(noCacheUrl).then(({authenticated, disabledApiSecurity}) => { - this.setState({ - user: { - loggedIn: authenticated, - }, - showLandingPopup: !authenticated || disabledApiSecurity, - disabledApiSecurity - }) - }); - } + const { oceLocale } = localStorage; + if (oceLocale && this.constructor.TRANSLATIONS[oceLocale]) { + this.state.locale = oceLocale; + } else { + this.state.locale = 'en_US'; + } - fetchIndicatorTypesMapping(){ - fetchJson('/api/indicatorTypesMapping').then(data => this.setState({indicatorTypesMapping: data})); - } + localStorage.alreadyVisited = true; - fetchYears(){ - fetchJson('/api/tendersAwardsYears').then(data => { - const years = data.map(pluck('_id')); - const {allMonths, currentFiltersState, appliedFilters} = this.state; - this.setState({ - currentFiltersState: currentFiltersState - .set('years', Set(years)) - .set('months', Set(allMonths)), - appliedFilters: appliedFilters - .set('years', Set(years)) - .set('months', Set(allMonths)), - allYears: years - }); - }); + this.destructFilters = cacheFn(filters => ({ + filters: filters.delete('years').delete('months'), + years: filters.get('years', Set()), + months: filters.get('months', Set()), + })); } - componentDidMount(){ + componentDidMount() { this.fetchUserInfo(); this.fetchIndicatorTypesMapping(); this.fetchYears(); + // eslint-disable-next-line react/no-did-mount-set-state this.setState({ - width: document.querySelector('.content').offsetWidth - 30 + width: document.querySelector('.content').offsetWidth - 30, }); - window.addEventListener("resize", debounce(() => { + window.addEventListener('resize', debounce(() => { this.setState({ - width: document.querySelector('.content').offsetWidth - 30 + width: document.querySelector('.content').offsetWidth - 30, }); })); } - toggleDashboardSwitcher(e){ - e.stopPropagation(); - const {dashboardSwitcherOpen} = this.state; - this.setState({dashboardSwitcherOpen: !dashboardSwitcherOpen}); - } - - loginBox(){ - if(this.state.user.loggedIn){ - return ( - - - - ) - } - return - - + setLocale(locale) { + this.setState({ locale }); + localStorage.oceLocale = locale; } - getPage(){ - const {translations} = this.props; + getPage() { + const { route, navigate } = this.props; const styling = this.constructor.STYLING || this.props.styling; - const {page, appliedFilters, indicatorTypesMapping, width} = this.state; + const [page] = route; - const {filters, years, months} = this.destructFilters(appliedFilters); - const monthly = years.count() == 1; + const { indicatorTypesMapping, width } = this.state; - if(page == 'overview'){ - return ; - } else if(page == 'corruption-type') { - const {corruptionType} = this.state; + if (page === 'type') { + const [, corruptionType] = route; const indicators = - Object.keys(indicatorTypesMapping).filter(key => indicatorTypesMapping[key].types.indexOf(corruptionType) > -1); + Object.keys(indicatorTypesMapping).filter(key => + indicatorTypesMapping[key].types.indexOf(corruptionType) > -1); return ( this.setState({page: 'individual-indicator', individualIndicator})} - filters={filters} - translations={translations} + onGotoIndicator={individualIndicator => navigate('indicator', corruptionType, individualIndicator)} corruptionType={corruptionType} - years={years} - monthly={monthly} - months={months} width={width} styling={styling} /> ); - } else if(page == 'individual-indicator'){ - const {individualIndicator} = this.state; + } else if (page === 'indicator') { + const [, corruptionType, individualIndicator] = route; return ( - ) + ); + } else if (page === 'contracts') { + return this.renderArchive(ContractsPage, 'contracts'); + } else if (page === 'contract') { + return this.renderSingle({ + Component: ContractPage, + sgSlug: 'contract', + plSlug: 'contracts', + additionalProps: { + totalContracts: this.state.data.getIn(['totalFlags', 'contractCounter'], 0), + }, + }); + } else if (page === 'suppliers') { + return this.renderArchive(SuppliersPage, 'suppliers'); + } else if (page === 'supplier') { + return this.renderSingle({ + Component: SupplierPage, + sgSlug: 'supplier', + plSlug: 'suppliers', + }); + } else if (page === 'procuring-entities') { + return this.renderArchive(ProcuringEntitiesPage, 'procuring-entities'); + } else if (page === 'procuring-entity') { + return this.renderSingle({ + Component: ProcuringEntityPage, + sgSlug: 'procuring-entity', + plSlug: 'procuring-entities', + }); } + return ( + + ); } - render(){ - const {dashboardSwitcherOpen, corruptionType, page, filterBoxIndex, currentFiltersState, appliedFilters - , data, indicatorTypesMapping, allYears, allMonths, showLandingPopup, disabledApiSecurity, user} = this.state; - const {onSwitch, translations} = this.props; + getTranslations() { + const { TRANSLATIONS } = this.constructor; + const { locale } = this.state; + return TRANSLATIONS[locale]; + } + + wireProps(_slug) { + const slug = Array.isArray(_slug) ? _slug : [_slug]; + const translations = this.getTranslations(); + const { appliedFilters, allYears, width } = this.state; + const { filters, years: selectedYears, months } = this.destructFilters(appliedFilters); + const years = Set(allYears).equals(selectedYears) ? + Set() : + selectedYears; + + return { + translations, + data: this.state.data.getIn(slug, Map()), + requestNewData: (path, newData) => + this.setState({ data: this.state.data.setIn(slug.concat(path), newData) }), + filters, + years, + monthly: years.count() === 1, + months, + width, + }; + } - const {filters, years, months} = this.destructFilters(appliedFilters); - const monthly = years.count() == 1; + t(str) { + const { locale } = this.state; + const { TRANSLATIONS } = this.constructor; + return TRANSLATIONS[locale][str] || TRANSLATIONS.en_US[str] || str; + } + fetchUserInfo() { + const noCacheUrl = new URI('/isAuthenticated').addSearch('time', Date.now()); + fetchJson(noCacheUrl).then(({ authenticated, disabledApiSecurity }) => { + this.setState({ + user: { + loggedIn: authenticated, + }, + showLandingPopup: !authenticated || disabledApiSecurity, + disabledApiSecurity, + }); + }); + } + + fetchIndicatorTypesMapping() { + fetchJson('/api/indicatorTypesMapping').then(data => this.setState({ indicatorTypesMapping: data })); + } + + fetchYears() { + fetchJson('/api/tendersAwardsYears').then((data) => { + const years = data.map(pluck('_id')); + const { allMonths, currentFiltersState, appliedFilters } = this.state; + this.setState({ + currentFiltersState: currentFiltersState + .set('years', Set(years)) + .set('months', Set(allMonths)), + appliedFilters: appliedFilters + .set('years', Set(years)) + .set('months', Set(allMonths)), + allYears: years, + }); + }); + } + + toggleDashboardSwitcher(e) { + e.stopPropagation(); + const { dashboardSwitcherOpen } = this.state; + this.setState({ dashboardSwitcherOpen: !dashboardSwitcherOpen }); + } + + loginBox() { + if (this.state.user.loggedIn) { + return ( + + + + ); + } + const hash = encodeURIComponent(location.hash); return ( -
this.setState({dashboardSwitcherOpen: false, filterBoxIndex: null})} + + + + ); + } + + languageSwitcher() { + const { TRANSLATIONS } = this.constructor; + const { locale: selectedLocale } = this.state; + if (Object.keys(TRANSLATIONS).length <= 1) return null; + return Object.keys(TRANSLATIONS).map(locale => ( + this.setLocale(locale)} + className={cn({ active: locale === selectedLocale })} + > + {locale.split('_')[0]} + + )); + } + + renderArchive(Component, slug) { + const { navigate, route } = this.props; + const [, searchQuery] = route; + return ( + navigate(slug, query)} + navigate={navigate} + /> + ); + } + + renderSingle({ Component, sgSlug, plSlug, additionalProps }) { + const { route, navigate, styling } = this.props; + const { indicatorTypesMapping } = this.state; + const [, id] = route; + return ( + navigate(plSlug, query)} + navigate={navigate} + indicatorTypesMapping={indicatorTypesMapping} + {...additionalProps} + /> + ); + } + + render() { + const { dashboardSwitcherOpen, filterBoxIndex, currentFiltersState, appliedFilters, data, + indicatorTypesMapping, allYears, allMonths, showLandingPopup, + disabledApiSecurity } = this.state; + + const { onSwitch, route, navigate } = this.props; + const translations = this.getTranslations(); + const [page] = route; + + return ( +
this.setState({ dashboardSwitcherOpen: false, filterBoxIndex: null })} > {showLandingPopup && - this.setState({showLandingPopup: false})}/>} + this.setState({ showLandingPopup: false })} + translations={translations} + languageSwitcher={(...args) => this.languageSwitcher(...args)} + /> + }
-
- -
-

- Corruption Risk Dashboard - + -
- {!disabledApiSecurity && this.loginBox()} -
-
+
+ + {this.languageSwitcher()} + + + {!disabledApiSecurity && this.loginBox()} +

this.setState({currentFiltersState})} - onApply={filtersToApply => this.setState({filterBoxIndex: null, appliedFilters: filtersToApply, currentFiltersState: filtersToApply})} + onUpdate={newState => this.setState({ currentFiltersState: newState })} + onApply={filtersToApply => { + CRDFilters.assign('CRD Dash', filtersToApply); + this.setState({ + filterBoxIndex: null, + appliedFilters: filtersToApply, + currentFiltersState: filtersToApply, + }) + }} translations={translations} currentBoxIndex={filterBoxIndex} - requestNewBox={index => this.setState({filterBoxIndex: index})} + requestNewBox={index => this.setState({ filterBoxIndex: index })} state={currentFiltersState} appliedFilters={appliedFilters} allYears={allYears} allMonths={allMonths} /> - -
+ + this.setState({ data: this.state.data.setIn(path, newData) })} + allYears={allYears} + /> +
{this.getPage()}
- ) + ); } } +CorruptionRiskDashboard.propTypes = { + translations: PropTypes.object.isRequired, + styling: PropTypes.object.isRequired, + onSwitch: PropTypes.func.isRequired, + route: PropTypes.array.isRequired, + navigate: PropTypes.func.isRequired +}; + +CorruptionRiskDashboard.TRANSLATIONS = { + en_US: require('../../../web/public/languages/en_US.json'), + es_ES: require('../../../web/public/languages/es_ES.json'), + fr_FR: require('../../../web/public/languages/fr_FR.json'), +}; + export default CorruptionRiskDashboard; diff --git a/ui/oce/corruption-risk/individual-indicator.jsx b/ui/oce/corruption-risk/individual-indicator.jsx deleted file mode 100644 index 406cb07c7..000000000 --- a/ui/oce/corruption-risk/individual-indicator.jsx +++ /dev/null @@ -1,206 +0,0 @@ -import CustomPopupChart from "./custom-popup-chart"; -import {Map} from "immutable"; -import {pluckImm} from "../tools"; -import Table from "../visualizations/tables/index"; -import translatable from "../translatable"; -import CRDPage from "./page"; -import {colorLuminance} from "./tools"; - -class IndividualIndicatorChart extends CustomPopupChart{ - getCustomEP(){ - const {indicator} = this.props; - return `flags/${indicator}/stats`; - } - - getData(){ - const data = super.getData(); - const {traceColors} = this.props.styling.charts; - if(!data) return []; - const {monthly} = this.props; - let dates = monthly ? - data.map(datum => { - const month = datum.get('month'); - return this.t(`general:months:${month}`); - }).toJS() : - data.map(pluckImm('year')).toJS(); - - let totalTrueValues = data.map(pluckImm('totalTrue', 0)).toJS(); - let totalPrecondMetValues = data.map(pluckImm('totalPrecondMet', 0)).toJS(); - - if(dates.length == 1){ - dates.unshift(""); - dates.push(" "); - totalTrueValues.unshift(0); - totalTrueValues.push(0); - totalPrecondMetValues.unshift(0); - totalPrecondMetValues.push(0); - } - - return [{ - x: dates, - y: totalTrueValues, - type: 'scatter', - fill: 'tonexty', - name: 'Flagged Procurements', - hoverinfo: 'none', - fillcolor: traceColors[0], - line: { - color: colorLuminance(traceColors[0], -.3), - }, - }, { - x: dates, - y: totalPrecondMetValues, - type: 'scatter', - fill: 'tonexty', - name: 'Eligible Procurements', - hoverinfo: 'none', - fillcolor: traceColors[1], - line: { - color: colorLuminance(traceColors[1], -.3) - } - }]; - } - - getLayout(){ - return { - legend: { - orientation: 'h', - xanchor: 'right', - yanchor: 'bottom', - x: 1, - y: 1 - }, - hovermode: 'closest', - xaxis: { - type: 'category', - showgrid: false - }, - yaxis: {} - } - } - - getPopup(){ - const {indicator, monthly} = this.props; - const {popup} = this.state; - const {year} = popup; - const data = super.getData(); - if(!data) return null; - let datum; - if(monthly){ - datum = data.find(datum => { - const month = datum.get('month'); - return year == this.t(`general:months:${month}`); - }) - } else { - datum = data.find(datum => datum.get('year') == year); - } - return ( -
-
-
- {year} -
-
-
-
-
Procurements Flagged
-
{datum.get('totalTrue')}
-
Eligible Procurements
-
{datum.get('totalPrecondMet')}
-
% Eligible Procurements Flagged
-
{datum.get('percentTruePrecondMet').toFixed(2)} %
-
% Procurements Eligible
-
{datum.get('percentPrecondMet').toFixed(2)} %
-
-
-
- ) - } -} - -import ProcurementsTable from "./procurements-table"; - -class ProjectTable extends ProcurementsTable{ - getCustomEP(){ - const {indicator} = this.props; - return `flags/${indicator}/releases?pageSize=10`; - } - - getClassName(){ - return "table-project-table"; - } -} - -class IndividualIndicatorPage extends translatable(CRDPage){ - constructor(...args){ - super(...args); - this.state = { - } - } - - render(){ - const {chart, table} = this.state; - const {indicator, translations, filters, years, monthly, months, width - , styling} = this.props; - return ( -
-

{this.t(`crd:indicators:${indicator}:name`)}

-

- {this.t("crd:indicators:general:indicator")} -   - {this.t(`crd:indicators:${indicator}:indicator`)} -

-

- {this.t("crd:indicators:general:eligibility")} -   - {this.t(`crd:indicators:${indicator}:eligibility`)} -

-

- {this.t("crd:indicators:general:thresholds")} -   - {this.t(`crd:indicators:${indicator}:thresholds`)} -

-

- {this.t("crd:indicators:general:description")} -   - {this.t(`crd:indicators:${indicator}:description`)} -

-
-

- Eligible Procurements and Flagged Procurements for {this.t(`crd:indicators:${indicator}:name`)} -

- this.setState({chart: data})} - data={chart} - width={width - 20} - styling={styling} - margin={{t: 0, b: 80, r: 40, pad: 40}} - /> -
-
-

- List of Procurements Flagged for {this.t(`crd:indicators:${indicator}:name`)} -

- this.setState({table: data})} - data={table} - translations={translations} - filters={filters} - years={years} - monthly={monthly} - months={months} - /> -
-
- ) - } -} - -export default IndividualIndicatorPage; diff --git a/ui/oce/corruption-risk/individual-indicator/index.jsx b/ui/oce/corruption-risk/individual-indicator/index.jsx new file mode 100644 index 000000000..a97beec3e --- /dev/null +++ b/ui/oce/corruption-risk/individual-indicator/index.jsx @@ -0,0 +1,212 @@ +import CustomPopupChart from '../custom-popup-chart'; +import { pluckImm } from '../../tools'; +import translatable from '../../translatable'; +import CRDPage from '../page'; +import { colorLuminance, sortByField } from '../tools'; +import ProcurementsTable from './table'; + +class IndividualIndicatorChart extends CustomPopupChart { + getCustomEP() { + const { indicator } = this.props; + return `flags/${indicator}/stats`; + } + + getData() { + let data = super.getData(); + const { traceColors } = this.props.styling.charts; + if (!data) return []; + const { monthly } = this.props; + + data = data.sort(sortByField(monthly ? 'month' : 'year')); + + const dates = monthly ? + data.map((datum) => { + const month = datum.get('month'); + return this.t(`general:months:${month}`); + }).toJS() : + data.map(pluckImm('year')).toJS(); + + const totalTrueValues = data.map(pluckImm('totalTrue', 0)).toJS(); + const totalPrecondMetValues = data.map(pluckImm('totalPrecondMet', 0)).toJS(); + + if (dates.length === 1) { + dates.unshift(''); + dates.push(' '); + totalTrueValues.unshift(0); + totalTrueValues.push(0); + totalPrecondMetValues.unshift(0); + totalPrecondMetValues.push(0); + } + + return [{ + x: dates, + y: totalTrueValues, + type: 'scatter', + fill: 'tonexty', + name: this.t('crd:individualIndicatorChart:flaggedProcurements'), + hoverinfo: 'none', + fillcolor: traceColors[0], + line: { + color: colorLuminance(traceColors[0], -0.3), + }, + }, { + x: dates, + y: totalPrecondMetValues, + type: 'scatter', + fill: 'tonexty', + name: this.t('crd:individualIndicatorChart:eligibleProcurements'), + hoverinfo: 'none', + fillcolor: traceColors[1], + line: { + color: colorLuminance(traceColors[1], -0.3), + }, + }]; + } + + getLayout() { + return { + legend: { + orientation: 'h', + xanchor: 'right', + yanchor: 'bottom', + x: 1, + y: 1, + }, + hovermode: 'closest', + xaxis: { + type: 'category', + showgrid: false, + }, + yaxis: {}, + }; + } + + getPopupWidth() { + const label = this.t('crd:indicatorPage:individualIndicatorChart:popup:percentEligible'); + return label.length > 23 ? + 500: + 350; + } + + getPopup() { + const { monthly } = this.props; + const { popup } = this.state; + const { year } = popup; + const data = super.getData(); + if (!data) return null; + let datum; + if (monthly) { + datum = data.find((datum) => { + const month = datum.get('month'); + return year === this.t(`general:months:${month}`); + }); + } else { + datum = data.find(datum => datum.get('year') === year); + } + return ( +
+
+
+ {year} +
+
+
+
+
{this.t('crd:indicatorPage:individualIndicatorChart:popup:procurementsFlagged')}
+
{datum.get('totalTrue')}
+
{this.t('crd:indicatorPage:individualIndicatorChart:popup:eligibleProcurements')}
+
{datum.get('totalPrecondMet')}
+
{this.t('crd:indicatorPage:individualIndicatorChart:popup:percentOfEligibleFlagged')}
+
{datum.get('percentTruePrecondMet').toFixed(2)} %
+
+ {this.t('crd:indicatorPage:individualIndicatorChart:popup:percentEligible')} +
+
{datum.get('percentPrecondMet').toFixed(2)} %
+
+
+
+ ); + } +} + +class IndividualIndicatorPage extends translatable(CRDPage) { + constructor(...args) { + super(...args); + this.state = {}; + } + + render() { + const { chart, table } = this.state; + const { corruptionType, indicator, translations, filters, years, monthly, months, width + , styling, navigate } = this.props; + + return ( +
+

{this.t(`crd:indicators:${indicator}:name`)}

+

+ {this.t('crd:indicators:general:indicator')} +   + {this.t(`crd:indicators:${indicator}:indicator`)} +

+

+ {this.t('crd:indicators:general:eligibility')} +   + {this.t(`crd:indicators:${indicator}:eligibility`)} +

+

+ {this.t('crd:indicators:general:thresholds')} +   + {this.t(`crd:indicators:${indicator}:thresholds`)} +

+

+ {this.t('crd:indicators:general:description')} +   + {this.t(`crd:indicators:${indicator}:description`)} +

+
+

+ {this.t('crd:indicatorPage:individualIndicatorChart:title').replace('$#$', this.t(`crd:indicators:${indicator}:name`))} +

+ this.setState({ chart: data })} + data={chart} + width={width - 20} + styling={styling} + margin={{ t: 0, b: 80, r: 40, pad: 40 }} + /> +
+
+

+ {this.t('crd:indicatorPage:projectTable:title').replace('$#$', this.t(`crd:indicators:${indicator}:name`))} +

+ this.setState({ table: data })} + data={table} + translations={translations} + filters={filters} + years={years} + monthly={monthly} + months={months} + navigate={navigate} + /> +
+
+ ); + } +} + +export default IndividualIndicatorPage; diff --git a/ui/oce/corruption-risk/individual-indicator/table.jsx b/ui/oce/corruption-risk/individual-indicator/table.jsx new file mode 100644 index 000000000..adad2666f --- /dev/null +++ b/ui/oce/corruption-risk/individual-indicator/table.jsx @@ -0,0 +1,194 @@ +import ReactDOM from 'react-dom'; +import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; +import { List } from 'immutable'; +import translatable from '../../translatable'; +import { POPUP_HEIGHT } from '../constants'; +import { getAwardAmount, mkContractLink, _3LineText } from '../tools'; +import PaginatedTable from '../paginated-table'; + +// eslint-disable-next-line no-undef +class Popup extends translatable(React.Component) { + constructor(...args) { + super(...args); + this.state = { + showPopup: false, + }; + } + + getPopup() { + const { type, flagIds } = this.props; + const { popupTop } = this.state; + return ( +
+
+
+
{this.t('crd:procurementsTable:associatedFlags').replace('$#$', this.t(`crd:corruptionType:${type}:name`))}
+
+
+
+
+
+ {flagIds.map(flagId =>

{this.t(`crd:indicators:${flagId}:name`)}

)} +
+
+
+
+ ); + } + + showPopup() { + const el = ReactDOM.findDOMNode(this); + this.setState({ + showPopup: true, + popupTop: -(POPUP_HEIGHT / 2) + (el.offsetHeight / 4), + }); + } + + render() { + const { flaggedStats, type } = this.props; + const { showPopup } = this.state; + let count = 0; + if (flaggedStats.has('count')) { + count = flaggedStats.get('count'); + } else { + count = flaggedStats.find(stat => stat.get('type') === type).get('count') + } + + return ( +
this.showPopup()} + onMouseLeave={() => this.setState({ showPopup: false })} + > + {count} + {showPopup && this.getPopup()} +
+ ); + } +} + +class ProcurementsTable extends PaginatedTable { + renderPopup({ flaggedStats, flagType: type, flagIds }) { + const { translations } = this.props; + return ( + + ); + } + + render() { + const { data, navigate, corruptionType } = this.props; + + if (!data) return null; + + const contracts = data.get('data', List()); + const count = data.getIn(['count', 0, 'count'], 0); + + const { pageSize, page } = this.state; + + const jsData = contracts.map((contract) => { + const tenderAmount = contract.getIn(['tender', 'value', 'amount'], 'N/A') + + ' ' + + contract.getIn(['tender', 'value', 'currency'], ''); + + const tenderPeriod = contract.get('tenderPeriod'); + const startDate = new Date(tenderPeriod.get('startDate')).toLocaleDateString(); + const endDate = new Date(tenderPeriod.get('endDate')).toLocaleDateString(); + + const flags = contract.get('flags'); + const flaggedStats = flags.get('flaggedStats'); + const flagType = flaggedStats.get('type', corruptionType); + const flagIds = + flags + .filter( + flag => flag.has && flag.has('types') && flag.get('types').includes(flagType) && flag.get('value') + ) + .keySeq(); + + return { + status: contract.getIn( + ['tender', 'status'], + contract.get('status', 'N/A') + ), + id: contract.get('ocid'), + title: contract.get('title', 'N/A'), + PEName: contract.getIn(['procuringEntity', 'name'], 'N/A'), + tenderAmount, + awardsAmount: getAwardAmount(contract), + tenderDate: `${startDate}—${endDate}`, + flagTypeName: this.t(`crd:corruptionType:${flagType}:name`), + // needed for the popup: + flaggedStats, + flagType, + flagIds, + }; + }); + + return ( + this.setState({ page: newPage }), + sizePerPage: pageSize, + sizePerPageList: [20, 50, 100, 200].map(value => ({ text: value, value })), + onSizePerPageList: newPageSize => this.setState({ pageSize: newPageSize }), + paginationPosition: 'both', + }} + > + + {this.t('crd:procurementsTable:status')} + + + + {this.t('crd:procurementsTable:contractID')} + + + + {this.t('crd:procurementsTable:title')} + + + + {this.t('crd:procurementsTable:procuringEntity')} + + + + {this.t('crd:procurementsTable:tenderAmount')} + + + + {this.t('crd:procurementsTable:awardsAmount')} + + + + {this.t('crd:procurementsTable:tenderDate')} + + + this.renderPopup(popupData)} + columnClassName="hoverable popup-left" + > + {this.t('crd:procurementsTable:individualIndicator:noOfFlags').replace( + '$#$', + this.t(`crd:corruptionType:${corruptionType}:name`) + )} + + + ); + } +} + +export default ProcurementsTable; diff --git a/ui/oce/corruption-risk/landing-popup.jsx b/ui/oce/corruption-risk/landing-popup.jsx index b8dea860e..801de0693 100644 --- a/ui/oce/corruption-risk/landing-popup.jsx +++ b/ui/oce/corruption-risk/landing-popup.jsx @@ -1,7 +1,8 @@ -import {LOGIN_URL} from "./constants"; -import {debounce} from "../tools"; +import { LOGIN_URL } from "./constants"; +import { debounce } from "../tools"; +import translatable from '../translatable'; -class LandingPopup extends React.Component{ +class LandingPopup extends translatable(React.Component) { constructor(...args){ super(...args); this.state = { @@ -12,7 +13,8 @@ class LandingPopup extends React.Component{ onClose(){ const {redirectToLogin, requestClosing} = this.props; if(redirectToLogin){ - location.href = LOGIN_URL; + const hash = encodeURIComponent(location.hash); + location.href = `${LOGIN_URL}${hash}`; } else { requestClosing(); } @@ -35,31 +37,36 @@ class LandingPopup extends React.Component{ } render(){ - const {top} = this.state; + const { top } = this.state; + const { languageSwitcher } = this.props; return (
-
+
-
+
-
-

Corruption Risk Dashboard

+
+

{this.t('crd:title')}

-
+
+ {languageSwitcher()} +
+
- The Corruption Risk Dashboard (CRD) is an open source tool that aims to help users understand the potential presence of corruption in public contracting. Through a red flagging approach (highlighting procurement activities that have certain risk factors), this prototype explores corruption risk through the visualization of 10 indicators that are mapped to three different forms of corruption: fraud, collusion, and process rigging. Users are free to explore the data, which has been converted to the Open Contracting Data Standard (OCDS), using a variety of filters. A crosstab table enables users to explore the overlap between any two indicators that are mapped to the same risk type. -
-
- The methodological approach that informs the CRD, which was built by Development Gateway (DG) with the collaboration and support of the Open Contracting Partnership (OCP), is presented in a co-authored research paper. Explanations of the principal concepts and indicators are available within the Dashboard. + {this.t('crd:landing:introduction:1')}
+

@@ -69,19 +76,19 @@ class LandingPopup extends React.Component{
- Intended Use + {this.t('crd:landing:intendedUse:title')}
- The CRD was designed with the primary objective of supporting two specific use cases: + {this.t('crd:landing:intendedUse:introduction')}
- 1. To facilitate the efforts of procurement regulators and informed observers to identify individual contracts that may warrant investigation, and; + {this.t('crd:landing:intendedUse:introduction:1')}
- 2. To aid these individuals to monitor corruption risk in procurement markets over time. + {this.t('crd:landing:intendedUse:introduction:2')}
@@ -91,32 +98,32 @@ class LandingPopup extends React.Component{
-
- Distinguishing between “Corruption” and “Corruption Risk” -
+
- While flags associated with a procurement process may indicate the possibility that corruption has taken place, they may also be attributable to poor data quality, systemic weaknesses, or practices that may be appropriate when specifically authorized by a procurement authority or regulatory institution. For this reason, this tool is best viewed as a mechanism for identifying the “risk” of corruption, rather than “corruption” itself. + {this.t('crd:landing:distinguishing:1')}
- Furthermore, in some instances, a single flag - such as for the awarding of two contracts to the same supplier by a single procuring entity - may not show any evidence of wrongdoing, though the confluence of multiple indicators suggests greater cause of concern. + {this.t('crd:landing:distinguishing:2')}

-
Your feedback is welcome
+
{this.t('crd:landing:feedback:title')}
-
- We welcome your feedback on this prototype. Please contact Andrew Mandelbaum at DG if you have any questions or suggestions:  - amandelbaum@developmentgateway.org -
+
diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx deleted file mode 100644 index 0b967f6af..000000000 --- a/ui/oce/corruption-risk/overview-page.jsx +++ /dev/null @@ -1,188 +0,0 @@ -import {Map} from "immutable"; -import {pluck, range} from "../tools"; -import Table from "../visualizations/tables/index"; -import ReactDOMServer from "react-dom/server"; -import CustomPopupChart from "./custom-popup-chart"; -import CRDPage from "./page"; -import {colorLuminance} from "./tools"; - -const pluckObj = (field, obj) => Object.keys(obj).map(key => obj[key][field]); - -class CorruptionType extends CustomPopupChart{ - groupData(data){ - let grouped = { - COLLUSION: {}, - FRAUD: {}, - RIGGING: {} - }; - const {monthly} = this.props; - data.forEach(datum => { - const type = datum.get('type'); - let date; - if(monthly){ - const month = datum.get('month'); - date = this.t(`general:months:${month}`); - } else { - date = datum.get('year'); - } - grouped[type] = grouped[type] || {}; - grouped[type][date] = datum.toJS(); - }); - - return grouped; - } - - getData(){ - const data = super.getData(); - if(!data) return []; - const {styling, months, monthly, years} = this.props; - const grouped = this.groupData(data); - return Object.keys(grouped).map((type, index) => { - const dataForType = grouped[type]; - let values = [], dates = []; - if(monthly){ - dates = range(1, 12) - .filter(month => months.has(month)) - .map(month => this.t(`general:months:${month}`)); - - values = dates.map(month => dataForType[month] ? dataForType[month].flaggedCount : 0); - } else { - dates = years.sort().toArray(); - values = dates.map(year => dataForType[year] ? dataForType[year].flaggedCount : 0); - } - - if(dates.length == 1){ - dates.unshift(""); - dates.push(" "); - values.unshift(0); - values.push(0); - } - - return { - x: dates, - y: values, - type: 'scatter', - fill: 'tonexty', - name: type, - fillcolor: styling.charts.traceColors[index], - line: { - color: colorLuminance(styling.charts.traceColors[index], -.3) - } - } - }); - } - - getLayout(){ - return { - hovermode: 'closest', - xaxis: { - type: 'category' - }, - yaxis: {}, - legend: { - orientation: 'h', - xanchor: 'right', - yanchor: 'bottom', - x: 1, - y: 1 - } - } - } - - getPopup(){ - const {popup} = this.state; - const {year, traceName: corruptionType} = popup; - const {indicatorTypesMapping} = this.props; - const data = this.groupData(super.getData()); - if(!data[corruptionType]) return null; - const dataForPoint = data[corruptionType][year]; - if(!dataForPoint) return null; - const indicatorCount = - Object.keys(indicatorTypesMapping).filter(indicatorId => - indicatorTypesMapping[indicatorId].types.indexOf(dataForPoint.type) > -1 - ).length; - - return ( -
-
-
- {year} -
-
-
-
-
Indicators
-
{indicatorCount}
-
Total Flags
-
{dataForPoint.flaggedCount}
-
Total Procurements Flagged
-
{dataForPoint.flaggedProjectCount}
-
% Total Procurements Flagged
-
{dataForPoint.percent.toFixed(2)}%
-
-
-
- ) - } -} - -CorruptionType.endpoint = 'percentTotalProjectsFlaggedByYear'; - -import ProcurementsTable from "./procurements-table"; - -class TopFlaggedContracts extends ProcurementsTable{ - getClassName(){ - return "table-top-flagged-contracts"; - } -} - -TopFlaggedContracts.endpoint = 'corruptionRiskOverviewTable?pageSize=10'; - -class OverviewPage extends CRDPage{ - constructor(...args){ - super(...args); - this.state = { - corruptionType: null, - topFlaggedContracts: null - } - } - - render(){ - const {corruptionType, topFlaggedContracts} = this.state; - const {filters, translations, years, monthly, months, indicatorTypesMapping, styling, width} = this.props; - return ( -
-
-

Risk of Fraud, Collusion and Process Rigging Over Time

- this.setState({corruptionType})} - translations={translations} - data={corruptionType} - years={years} - monthly={monthly} - months={months} - styling={styling} - indicatorTypesMapping={indicatorTypesMapping} - width={width - 20} - margin={{t: 0, b: 40, r: 40, pad: 20}} - /> -
-
-

The Procurement Processes with the Most Flags

- this.setState({topFlaggedContracts})} - /> -
-
- ) - } -} - -export default OverviewPage; diff --git a/ui/oce/corruption-risk/overview-page/index.jsx b/ui/oce/corruption-risk/overview-page/index.jsx new file mode 100644 index 000000000..41143e2d8 --- /dev/null +++ b/ui/oce/corruption-risk/overview-page/index.jsx @@ -0,0 +1,193 @@ +import { range } from '../../tools'; +import CustomPopupChart from '../custom-popup-chart'; +import CRDPage from '../page'; +import { colorLuminance, wireProps } from '../tools'; +import ProcurementsTable from './table'; +import { POPUP_HEIGHT } from '../constants'; + +const TRACES = ['COLLUSION', 'FRAUD', 'RIGGING']; + +class CorruptionType extends CustomPopupChart { + groupData(data) { + const grouped = {}; + TRACES.forEach((trace) => { + grouped[trace] = {}; + }); + + const { monthly } = this.props; + data.forEach((datum) => { + const type = datum.get('type'); + let date; + if (monthly) { + const month = datum.get('month'); + date = this.t(`general:months:${month}`); + } else { + date = datum.get('year'); + } + grouped[type] = grouped[type] || {}; + grouped[type][date] = datum.toJS(); + }); + + return grouped; + } + + getData() { + const data = super.getData(); + if (!data) return []; + const { styling, months, monthly, years } = this.props; + const grouped = this.groupData(data); + + const commonYears = new Set(); + + if (!monthly) { + Object.values(grouped).forEach(corruptionType => + Object.keys(corruptionType).forEach(year => commonYears.add(year)) + ); + } + + return Object.keys(grouped).map((type, index) => { + const dataForType = grouped[type]; + let values = []; + let dates = []; + if (monthly) { + dates = range(1, 12) + .filter(month => months.has(month)) + .map(month => this.t(`general:months:${month}`)); + + values = dates.map(month => (dataForType[month] ? dataForType[month].flaggedCount : 0)); + } else if (years.count()) { + dates = years.sort().toArray(); + values = dates.map(year => (dataForType[year] ? dataForType[year].flaggedCount : 0)); + } else { + dates = Array.from(commonYears).sort(); + values = dates.map(year => dataForType[year] ? dataForType[year].flaggedCount : 0); + } + + if (dates.length === 1) { + dates.unshift(''); + dates.push(' '); + values.unshift(0); + values.push(0); + } + + return { + x: dates, + y: values, + type: 'scatter', + fill: 'tonexty', + name: this.t(`crd:corruptionType:${type}:name`), + fillcolor: styling.charts.traceColors[index], + line: { + color: colorLuminance(styling.charts.traceColors[index], -0.3), + }, + }; + }); + } + + getLayout() { + return { + hovermode: 'closest', + xaxis: { + type: 'category', + }, + yaxis: {}, + legend: { + orientation: 'h', + xanchor: 'right', + yanchor: 'bottom', + x: 1, + y: 1, + }, + }; + } + + getPopup() { + const { popup } = this.state; + const { year, traceIndex } = popup; + const corruptionType = TRACES[traceIndex]; + const { indicatorTypesMapping } = this.props; + const data = this.groupData(super.getData()); + if (!data[corruptionType]) return null; + const dataForPoint = data[corruptionType][year]; + if (!dataForPoint) return null; + const indicatorCount = + Object.keys(indicatorTypesMapping).filter(indicatorId => + indicatorTypesMapping[indicatorId].types.indexOf(dataForPoint.type) > -1 + ).length; + + const percentFlaggedLabel = this.t('crd:overview:overTimeChart:percentFlagged'); + + let height = POPUP_HEIGHT; + let { top } = popup; + if (percentFlaggedLabel.length > 30) { + const delta = 30; + height += delta; + if (popup.toTheLeft) { + top += delta / 2; + } + top -= delta; + } + + return ( +
+
+
+ {year} +
+
+
+
+
{this.t('crd:overview:overTimeChart:indicators')}
+
{indicatorCount}
+
{this.t('crd:overview:overTimeChart:totalFlags')}
+
{dataForPoint.flaggedCount}
+
{this.t('crd:overview:overTimeChart:totalProcurementsFlagged')}
+
{dataForPoint.flaggedProjectCount}
+
{percentFlaggedLabel}
+
{dataForPoint.percent.toFixed(2)}%
+
+
+
+ ); + } +} + +CorruptionType.endpoint = 'percentTotalProjectsFlaggedByYear'; + +class OverviewPage extends CRDPage { + constructor(...args) { + super(...args); + this.state = { + topFlaggedContracts: null, + }; + } + + render() { + const { indicatorTypesMapping, styling, width, navigate } = this.props; + return ( +
+
+

{this.t('crd:overview:overTimeChart:title')}

+ +
+
+

{this.t('crd:overview:topFlagged:title')}

+ +
+
+ ); + } +} + +export default OverviewPage; diff --git a/ui/oce/corruption-risk/overview-page/table.jsx b/ui/oce/corruption-risk/overview-page/table.jsx new file mode 100644 index 000000000..5fd88d294 --- /dev/null +++ b/ui/oce/corruption-risk/overview-page/table.jsx @@ -0,0 +1,191 @@ +import ReactDOM from 'react-dom'; +import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; +import { List } from 'immutable'; +import translatable from '../../translatable'; +import { POPUP_HEIGHT } from '../constants'; +import { getAwardAmount, mkContractLink, _3LineText } from '../tools'; +import PaginatedTable from '../paginated-table'; + +// eslint-disable-next-line no-undef +class Popup extends translatable(React.Component) { + constructor(...args) { + super(...args); + this.state = { + showPopup: false, + }; + } + + getPopup() { + const { type, flagIds } = this.props; + const { popupTop } = this.state; + return ( +
+
+
+
{this.t('crd:procurementsTable:associatedFlags').replace('$#$', this.t(`crd:corruptionType:${type}:name`))}
+
+
+
+
+
+ {flagIds.map(flagId =>

{this.t(`crd:indicators:${flagId}:name`)}

)} +
+
+
+
+ ); + } + + showPopup() { + const el = ReactDOM.findDOMNode(this); + this.setState({ + showPopup: true, + popupTop: -(POPUP_HEIGHT / 2) + (el.offsetHeight / 4), + }); + } + + render() { + const { flaggedStats, type } = this.props; + const { showPopup } = this.state; + + const count = flaggedStats.get('count', 0); + //flaggedStats.find(stat => stat.get('type') === type).get('count') + return ( +
this.showPopup()} + onMouseLeave={() => this.setState({ showPopup: false })} + > + {count} + {showPopup && this.getPopup()} +
+ ); + } +} + +class ProcurementsTable extends PaginatedTable { + renderPopup({ flaggedStats, flagType: type, flagIds }) { + const { translations } = this.props; + return ( + + ); + } + + render() { + const { data, navigate, corruptionType } = this.props; + + if (!data) return null; + + const contracts = data.get('data', List()); + const count = data.getIn(['count', 0, 'count'], 0); + + const { pageSize, page } = this.state; + + const jsData = contracts.map((contract) => { + const tenderAmount = contract.getIn(['tender', 'value', 'amount'], 'N/A') + + ' ' + + contract.getIn(['tender', 'value', 'currency'], ''); + + const tenderPeriod = contract.get('tenderPeriod'); + const startDate = new Date(tenderPeriod.get('startDate')).toLocaleDateString(); + const endDate = new Date(tenderPeriod.get('endDate')).toLocaleDateString(); + + const flags = contract.get('flags'); + const flaggedStats = flags.get('flaggedStats'); + const flagType = flaggedStats.get('type', corruptionType); + const flagIds = + flags + .filter( + flag => flag.has && flag.has('types') && flag.get('types').includes(flagType) && flag.get('value') + ) + .keySeq(); + + return { + status: contract.getIn( + ['tender', 'status'], + contract.get('status', 'N/A') + ), + id: contract.get('ocid'), + title: contract.get('title', 'N/A'), + PEName: contract.getIn(['procuringEntity', 'name'], 'N/A'), + tenderAmount, + awardsAmount: getAwardAmount(contract), + tenderDate: `${startDate}—${endDate}`, + flagTypeName: this.t(`crd:corruptionType:${flagType}:name`), + // needed for the popup: + flaggedStats, + flagType, + flagIds, + }; + }); + + return ( + this.setState({ page: newPage }), + sizePerPage: pageSize, + sizePerPageList: [20, 50, 100, 200].map(value => ({ text: value, value })), + onSizePerPageList: newPageSize => this.setState({ pageSize: newPageSize }), + paginationPosition: 'both', + }} + > + + {this.t('crd:procurementsTable:status')} + + + + {this.t('crd:procurementsTable:contractID')} + + + + {this.t('crd:procurementsTable:title')} + + + + {this.t('crd:procurementsTable:procuringEntity')} + + + + {this.t('crd:procurementsTable:tenderAmount')} + + + + {this.t('crd:procurementsTable:awardsAmount')} + + + + {this.t('crd:procurementsTable:tenderDate')} + + + + {this.t('crd:procurementsTable:flagType')} + + + this.renderPopup(popupData)} + columnClassName="hoverable popup-left" + > + {this.t('crd:procurementsTable:noOfFlags')} + + + ); + } +} + +export default ProcurementsTable; diff --git a/ui/oce/corruption-risk/page.jsx b/ui/oce/corruption-risk/page.jsx index 692e7c1c2..356bf5e8a 100644 --- a/ui/oce/corruption-risk/page.jsx +++ b/ui/oce/corruption-risk/page.jsx @@ -1,4 +1,6 @@ -class Page extends React.Component{ +import translatable from '../translatable'; + +class Page extends translatable(React.Component){ scrollTop(){ window.scrollTo(0, 0); } diff --git a/ui/oce/corruption-risk/paginated-table.jsx b/ui/oce/corruption-risk/paginated-table.jsx new file mode 100644 index 000000000..02803c9a1 --- /dev/null +++ b/ui/oce/corruption-risk/paginated-table.jsx @@ -0,0 +1,47 @@ +import URI from 'urijs'; +import Visualization from '../visualization'; + +class PaginatedTable extends Visualization { + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.pageSize = 20; + this.state.page = 1; + } + + getCustomEP() { + const { pageSize, page } = this.state; + const { dataEP, countEP } = this.props; + + let data = new URI(dataEP) + .addSearch('pageSize', pageSize) + .addSearch('pageNumber', page - 1); + + let count = new URI(countEP); + + return [ + data, + count, + ]; + } + + transform([data, count]) { + return { + data, + count, + }; + } + + componentDidUpdate(_, prevState) { + const stateChanged = ['pageSize', 'page'].some(key => this.state[key] !== prevState[key]); + if (stateChanged) { + this.fetch(); + } + } + + render() { + throw 'Abstract!'; + } +} + +export default PaginatedTable; diff --git a/ui/oce/corruption-risk/plotly-chart.jsx b/ui/oce/corruption-risk/plotly-chart.jsx new file mode 100644 index 000000000..b1dfcd097 --- /dev/null +++ b/ui/oce/corruption-risk/plotly-chart.jsx @@ -0,0 +1,46 @@ +import Plotly from 'plotly.js/lib/core'; +import ReactIgnore from '../react-ignore'; + +class PlotlyChart extends React.PureComponent { + componentDidMount() { + const { data, layout, onUpdate } = this.props; + Plotly.newPlot( + this.chartContainer, + data, + layout, + ); + if (onUpdate) this.chartContainer.on('plotly_afterplot', onUpdate); + } + + componentWillUnmount() { + Plotly.Plots.purge(this.chartContainer); + } + + componentDidUpdate(prevProps) { + const { data, layout } = this.props; + if (data !== prevProps[data]) { + this.chartContainer.data = data; + this.chartContainer.layout = layout; + setTimeout(() => Plotly.redraw(this.chartContainer)); + } else if (layout !== prevProps[layout]) { + setTimeout(() => Plotly.relayout(this.chartContainer, layout)); + } + } + + render() { + return ( +
+ +
{ this.chartContainer = c; }} /> + +
+ ); + } +} + +PlotlyChart.defaultProps = { + data: [], + layout: {}, +} + +export default PlotlyChart; diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx deleted file mode 100644 index d922b07e7..000000000 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import cn from "classnames"; -import Table from "../visualizations/tables"; -import translatable from "../translatable"; -import ReactDOM from "react-dom"; -import {POPUP_HEIGHT} from './constants'; - -class Popup extends translatable(React.Component){ - constructor(...args){ - super(...args); - this.state = { - showPopup: false - } - } - - getPopup(){ - const {type, flagIds} = this.props; - const {popupTop} = this.state; - return ( -
-
-
-
Associated {type[0] + type.substr(1).toLowerCase()} Flags
-
-
-
-
-
- {flagIds.map(flagId =>

{this.t(`crd:indicators:${flagId}:name`)}

)} -
-
-
-
- ) - } - - showPopup(){ - const el = ReactDOM.findDOMNode(this); - this.setState({ - showPopup: true, - popupTop: -(POPUP_HEIGHT / 2) + (el.offsetHeight / 4) - }); - } - - render(){ - const {flaggedStats} = this.props; - const {showPopup} = this.state; - return ( - this.setState({showPopup: false})} - > - {flaggedStats.get('count')} - {showPopup && this.getPopup()} - - ) - } -} - -class ProcurementsTable extends Table{ - row(entry, index){ - const {translations} = this.props; - const tenderValue = entry.getIn(['tender', 'value']); - const awardValue = entry.getIn(['awards', 0, 'value']); - const tenderPeriod = entry.get('tenderPeriod'); - const startDate = new Date(tenderPeriod.get('startDate')); - const endDate = new Date(tenderPeriod.get('endDate')); - const flags = entry.get('flags'); - const flaggedStats = flags.get('flaggedStats'); - const type = flaggedStats.get('type'); - const flagIds = - flags - .filter( - flag => flag.has && flag.has('types') && flag.get('types').includes(type) && flag.get('value') - ) - .keySeq(); - - const procuringEntityName = entry.getIn(['procuringEntity', 'name']); - const title = entry.get('title'); - - return ( - - {entry.get('tag', []).join(', ')} - {entry.get('ocid')} - -
- {title} -
- - -
- {procuringEntityName} -
- - {tenderValue && tenderValue.get('amount')} {tenderValue && tenderValue.get('currency')} - {awardValue.get('amount')} {awardValue.get('currency')} - {startDate.toLocaleDateString()}—{endDate.toLocaleDateString()} - {this.t(`crd:corruptionType:${type}:name`)} - - - ) - } - - render(){ - const {data} = this.props; - return ( - - - - - - - - - - - - - - - - {data && data.map(this.row.bind(this))} - -
StatusContract IDTitleProcuring EntityTender AmountAwards AmountTender DateFlag TypeNo. of Flags
- ) - } -} - -export default ProcurementsTable; diff --git a/ui/oce/corruption-risk/procuring-entities/index.jsx b/ui/oce/corruption-risk/procuring-entities/index.jsx new file mode 100644 index 000000000..e3507bb53 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/index.jsx @@ -0,0 +1,162 @@ +import { List } from 'immutable'; +import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; +import CRDPage from '../page'; +import PaginatedTable from '../paginated-table'; +import Archive from '../archive'; +import { wireProps } from '../tools'; +import { PEIds, TendersCount, AwardsCount } from './state'; + +export const mkLink = navigate => (content, { id }) => ( + navigate('procuring-entity', id)} + > + {content} + +); + +class PEList extends PaginatedTable { + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.tenders = {}; + this.state.awards = {}; + } + + getCustomEP() { + const { searchQuery } = this.props; + const eps = super.getCustomEP(); + return searchQuery ? + eps.map(ep => ep.addSearch('text', searchQuery)) : + eps; + } + + componentWillMount() { + TendersCount.addListener( + 'PEList', + this.updateBindings.bind(this), + ); + AwardsCount.addListener( + 'PEList', + this.updateBindings.bind(this), + ); + } + + updateBindings() { + Promise.all([ + TendersCount.getState(), + AwardsCount.getState(), + ]).then(([tenders, awards]) => { + this.setState({ tenders, awards }); + }); + } + + componentWillUnmount() { + TendersCount.removeListener('PEList'); + AwardsCount.removeListener('PEList'); + } + + componentDidUpdate(prevProps, prevState) { + const propsChanged = ['filters', 'searchQuery'].some(key => this.props[key] !== prevProps[key]); + if (propsChanged) { + this.fetch(); + } else { + super.componentDidUpdate(prevProps, prevState); + } + + const { data } = this.props; + if (prevProps.data !== data) { + PEIds.assign( + 'PEList', + this.props.data + .get('data', List()) + .map(datum => datum.get('procuringEntityId')) + ); + } + } + + render() { + const { data, navigate } = this.props; + + const count = data.get('count', 0); + + const { pageSize, page, tenders, awards } = this.state; + + const jsData = data.get('data', List()).map((supplier) => { + const id = supplier.get('procuringEntityId'); + return { + id, + name: supplier.get('procuringEntityName'), + nrTenders: tenders[id], + nrAwards: awards[id], + nrFlags: supplier.get('countFlags'), + } + }).toJS(); + + return ( + this.setState({ page: newPage }), + sizePerPage: pageSize, + sizePerPageList: [20, 50, 100, 200].map(value => ({ text: value, value })), + onSizePerPageList: newPageSize => this.setState({ pageSize: newPageSize }), + paginationPosition: 'both', + }} + > + + {this.t('crd:suppliers:ID')} + + + {this.t('crd:suppliers:name')} + + + {this.t('crd:procuringEntities:noOfTenders')} + + + {this.t('crd:procuringEntities:noOfAwards')} + + + {this.t('crd:procurementsTable:noOfFlags')} + + + ); + } +} + +class ProcuringEntities extends CRDPage { + requestNewData(path, newData) { + this.props.requestNewData( + path, + newData.set('count', newData.getIn(['count', 0, 'count'], 0)) + ); + } + + render() { + const { navigate, searchQuery, doSearch } = this.props; + return ( + + ); + } +} + +export default ProcuringEntities; diff --git a/ui/oce/corruption-risk/procuring-entities/single/general/by-method/index.jsx b/ui/oce/corruption-risk/procuring-entities/single/general/by-method/index.jsx new file mode 100644 index 000000000..c1c4e521c --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/general/by-method/index.jsx @@ -0,0 +1,87 @@ +import { BarChart, Bar, XAxis, YAxis, LabelList, ResponsiveContainer, Legend, Tooltip } from 'recharts'; +import translatable from '../../../../../translatable'; +import Popup from './popup'; +import { renderTopLeftLabel } from '../../../../archive/tools'; +import { maxCommonDataLength } from '../../state'; + +class ProcurementsByMethod extends translatable(React.PureComponent) { + constructor(props) { + super(props); + this.state = this.state || {}; + this.state.data = []; + this.state.length = 5; + } + + componentDidMount() { + const { zoomed, data } = this.props; + const name = zoomed ? 'ZoomedProcurementsByMethodChart' : 'ProcurementsByMethodChart'; + data.addListener(name, () => { + data.getState(name).then(data => { + this.setState({ + data + }) + }) + }); + maxCommonDataLength.addListener(name, () => { + maxCommonDataLength.getState(name).then(length => this.setState({ length })) + }); + } + + componentWillUnmount() { + const { zoomed, data } = this.props; + const name = zoomed ? 'ZoomedProcurementsByMethodChart' : 'ProcurementsByMethodChart'; + data.removeListener(name); + maxCommonDataLength.removeListener(name); + } + + render() { + const { translations, zoomed } = this.props; + let { data, length } = this.state; + + let height = 350; + if (zoomed) { + height = Math.max(height, data.length * 50); + } else { + data = data.slice(0, length); + if (data.length < length) { + for (let counter = data.length; counter < length; counter++) { + data.unshift({}); + } + } + height = length * 70; + } + + return ( +
+ + + + + } translations={translations} cursor={false} /> + + + + + +
+ ) + } +} + +export default ProcurementsByMethod; diff --git a/ui/oce/corruption-risk/procuring-entities/single/general/by-method/popup.jsx b/ui/oce/corruption-risk/procuring-entities/single/general/by-method/popup.jsx new file mode 100644 index 000000000..c43acd0e0 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/general/by-method/popup.jsx @@ -0,0 +1,33 @@ +import translatable from '../../../../../translatable'; + +class Popup extends translatable(React.PureComponent) { + render() { + const { coordinate, active, viewBox, payload } = this.props; + if (!active || !payload[0]) return null; + + const { status, count } = payload[0].payload; + + let POPUP_HEIGHT = 55; + + const style = { + left: 0, + top: coordinate.y - POPUP_HEIGHT - viewBox.top - 4, + width: 300, + height: POPUP_HEIGHT, + } + + return ( +
+
+ {status}: {count} +
+
+
+ ); + } +} + +export default Popup; diff --git a/ui/oce/corruption-risk/procuring-entities/single/general/by-status/index.jsx b/ui/oce/corruption-risk/procuring-entities/single/general/by-status/index.jsx new file mode 100644 index 000000000..b9ed49154 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/general/by-status/index.jsx @@ -0,0 +1,87 @@ +import { BarChart, Bar, XAxis, YAxis, LabelList, ResponsiveContainer, Legend, Tooltip } from 'recharts'; +import translatable from '../../../../../translatable'; +import Popup from './popup'; +import { renderTopLeftLabel } from '../../../../archive/tools'; +import { maxCommonDataLength } from '../../state'; + +class ProcurementsByStatus extends translatable(React.PureComponent) { + constructor(props) { + super(props); + this.state = this.state || {}; + this.state.data = []; + this.state.length = 5; + } + + componentDidMount() { + const { zoomed, data } = this.props; + const name = zoomed ? 'ZoomedProcurementsByStatusChart' : 'ProcurementsByStatusChart'; + data.addListener(name, () => { + data.getState(name).then(data => { + this.setState({ + data + }) + }) + }); + maxCommonDataLength.addListener(name, () => { + maxCommonDataLength.getState(name).then(length => this.setState({ length })) + }); + } + + componentWillUnmount() { + const { zoomed, data } = this.props; + const name = zoomed ? 'ZoomedProcurementsByStatusChart' : 'ProcurementsByStatusChart'; + data.removeListener(name); + maxCommonDataLength.removeListener(name); + } + + render() { + const { translations, zoomed } = this.props; + let { data, length } = this.state; + + let height = 350; + if (zoomed) { + height = Math.max(height, data.length * 50); + } else { + data = data.slice(0, 5); + if (data.length < length) { + for(let counter = data.length; counter < length; counter++) { + data.unshift({}); + } + } + height = length * 70; + } + + return ( +
+ + + + + } translations={translations} cursor={false} /> + + + + + +
+ ) + } +} + +export default ProcurementsByStatus; diff --git a/ui/oce/corruption-risk/procuring-entities/single/general/by-status/popup.jsx b/ui/oce/corruption-risk/procuring-entities/single/general/by-status/popup.jsx new file mode 100644 index 000000000..e4940e749 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/general/by-status/popup.jsx @@ -0,0 +1,33 @@ +import translatable from '../../../../../translatable'; + +class Popup extends translatable(React.PureComponent) { + render() { + const { coordinate, active, viewBox, payload } = this.props; + if (!active || !payload[0]) return null; + + const { status, count } = payload[0].payload; + + const POPUP_HEIGHT = 55; + + const style = { + left: 0, + top: coordinate.y - POPUP_HEIGHT - viewBox.top - 4, + width: 300, + height: POPUP_HEIGHT, + }; + + return ( +
+
+ {status}: {count} +
+
+
+ ); + } +} + +export default Popup; diff --git a/ui/oce/corruption-risk/procuring-entities/single/index.jsx b/ui/oce/corruption-risk/procuring-entities/single/index.jsx new file mode 100644 index 000000000..b88055c17 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/index.jsx @@ -0,0 +1,112 @@ +import TopSearch from '../../top-search'; +import translatable from '../../../translatable'; +import Info from './info'; +import { + PEId, + PEFlaggedNrData, + winsAndFlagsData, + procurementsByStatusData, + procurementsByMethodData, + PEInfo, + max2ndRowCommonDataLength +} from './state'; +import Zoomable from '../../zoomable'; +import TitleBelow from '../../archive/title-below'; +import WinsAndFlags from '../../suppliers/single/bars/wins-and-flags'; +import FlaggedNr from '../../suppliers/single/bars/flagged-nr'; +import ProcurementsByStatus from './general/by-status'; +import ProcurementsByMethod from './general/by-method'; +import ProcurementsTable from './table'; +import style from './style.less'; +import CRDPage from '../../page'; + +const NAME = 'ProcuringEntitySingle'; + +class ProcuringEntity extends CRDPage { + constructor(...args){ + super(...args); + this.state = this.state || {}; + } + + componentWillMount() { + const { id } = this.props; + PEId.assign('PESingleComponent', id); + PEInfo.addListener(NAME, () => { + PEInfo.getState().then(PEInfo => { + this.setState({ PEName: PEInfo.name }) + }); + }); + } + + componentDidUpdate() { + const { id } = this.props; + PEId.assign('PESingleComponent', id); + } + + componentWillUnmount() { + PEInfo.removeListener(NAME); + } + + render() { + const { translations, doSearch, width, navigate } = this.props; + const { PEName } = this.state; + + return ( +
+ + +
+

{this.t('crd:procuringEntities:generalStatistics')}

+
+
+ + + + + +
+
+ + + + + +
+
+
+
+

+ {this.t('crd:contracts:flagAnalysis')} +

+
+
+ + + + + +
+
+ + + + + +
+
+

Procurements by {PEName}

+ +
+
+ ); + } +} + +export default ProcuringEntity; diff --git a/ui/oce/corruption-risk/procuring-entities/single/info.jsx b/ui/oce/corruption-risk/procuring-entities/single/info.jsx new file mode 100644 index 000000000..b29f64aaa --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/info.jsx @@ -0,0 +1,101 @@ +import { + PEInfo, + PEFlagsCount, + associatedBuyers, + associatedContractsCount, + associatedUnflaggedContractsCount, +} from './state'; +import translatable from '../../../translatable'; +import boundComponent from '../../../state/bound-component'; + +class Cell extends React.PureComponent { + render() { + const { title, children, dlClassName, ...props } = this.props; + return ( + +
+
{title}
+
{children}
+
+ + ); + } +} + +class Info extends translatable(boundComponent({ + name: 'PE info', + deps: { + info: PEInfo, + flagsCount: PEFlagsCount, + buyers: associatedBuyers, + contractsCount: associatedContractsCount, + unflaggedContractsCount: associatedUnflaggedContractsCount, + } +})) { + render() { + const { info, flagsCount, buyers, contractsCount, unflaggedContractsCount } = this.state; + if (!info) return null; + const { address, contactPoint } = info; + + return ( +
+
+ + + + + {info.name} + + {info.id} + + + {buyers && buyers.length && + + + {buyers.map(buyer =>

{buyer.buyerName}

)} +
+ + } + +
+ Flag icon +   + + {flagsCount} +   + {this.t(flagsCount === 1 ? + 'crd:contracts:baseInfo:flag:sg' : + 'crd:contracts:baseInfo:flag:pl')} + + + {contractsCount} procurements flagged +
+ (Out of {unflaggedContractsCount} procurement won) +
+
+ + + + + {address.streetAddress}
+ {address.locality} / +   + {address.postalCode} / +   + {address.countryName} +
+ + {contactPoint.name}
+ {contactPoint.email}
+ {contactPoint.telephone} +
+ + +
+
+
+ ); + } +} + +export default Info; diff --git a/ui/oce/corruption-risk/procuring-entities/single/state.jsx b/ui/oce/corruption-risk/procuring-entities/single/state.jsx new file mode 100644 index 000000000..79eef48d5 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/state.jsx @@ -0,0 +1,190 @@ +import { Set } from 'immutable'; +import { CRD, datefulFilters, API_ROOT } from '../../../state/oce-state'; +import { FlaggedNrMapping } from '../../archive/state'; + +export const PEState = CRD.substate({ + name: 'PEState', +}); + +export const PEId = PEState.input({ + name: 'PEId', +}); + +export const PEFilters = PEState.mapping({ + name: 'PEFilters', + deps: [datefulFilters, PEId], + mapper: (filters, PEId) => + filters.update( + 'procuringEntityId', + Set(), + PEIds => PEIds.add(PEId) + ) +}); + +const PEInfoUrl = PEState.mapping({ + name: 'PEInfoUrl', + deps: [PEId], + mapper: id => `${API_ROOT}/ocds/organization/procuringEntity/id/${id}`, +}); + +export const PEInfo = PEState.remote({ + name: 'PEInfoRaw', + url: PEInfoUrl, +}); + +const PEFlagsUrl = PEState.input({ + name: 'PEFlagsUrl', + initial: `${API_ROOT}/totalFlags`, +}); + +const PEFlagsCountRaw = PEState.remote({ + name: 'PEFlagsCountRaw', + url: PEFlagsUrl, + params: PEFilters, +}); + +export const PEFlagsCount = PEState.mapping({ + name: 'PEFlagsCount', + deps: [PEFlagsCountRaw], + mapper: data => data[0].flaggedCount, +}); + +const contractsUrl = PEState.input({ + name: 'contractsUrl', + initial: `${API_ROOT}/flaggedRelease/all` +}) + +const associatedContracts = PEState.remote({ + name: 'associatedContracts', + url: contractsUrl, + params: PEFilters, +}); + +const associatedBuyersURL = PEState.input({ + name: 'associatedBuyersURL', + initial: `${API_ROOT}/buyersForProcuringEntities` +}); + +export const associatedBuyers = PEState.remote({ + name: 'associatedBuyers', + url: associatedBuyersURL, + params: PEFilters, +}); + +const contractsCountUrl = PEState.input({ + name: 'contractsCountUrl', + initial: `${API_ROOT}/flaggedRelease/count` +}); + +export const associatedContractsCount = PEState.remote({ + name: 'associatedContractsCount', + url: contractsCountUrl, + params: PEFilters, +}); + +const unflaggedContractsCountUrl = PEState.input({ + name: 'unflaggedContractsCountUrl', + initial: `${API_ROOT}/ocds/release/count`, +}); + +export const associatedUnflaggedContractsCount = PEState.remote({ + name: 'associatedUnflaggedContractsCount', + url: unflaggedContractsCountUrl, + params: PEFilters, +}); + +const associatedSuppliers = PEState.mapping({ + name: 'associatedSuppliers', + deps: ['associatedContracts'], +}) + +export const PEFlaggedNrData = new FlaggedNrMapping({ + name: 'PEFlaggedNrData', + filters: PEFilters, + parent: PEState, +}); + +const winsAndFlagsURL = PEState.input({ + name: 'winsAndFlagsURL', + initial: `${API_ROOT}/supplierWinsPerProcuringEntity`, +}); + +const winsAndFlagsRaw = PEState.remote({ + name: 'winsAndFlagsRaw', + url: winsAndFlagsURL, + params: PEFilters, +}); + +export const winsAndFlagsData = PEState.mapping({ + name: 'winsAndFlagsData', + deps: [winsAndFlagsRaw], + mapper: data => data.map(datum => { + return { + name: datum.supplierName, + wins: datum.count, + flags: datum.countFlags, + } + }) +}); + +const procurementsByMethodUrl = PEState.input({ + name: 'procurementsByMethodUrl', + initial: `${API_ROOT}/procurementsByProcurementMethod`, +}); + +const procurementsByMethodRaw = PEState.remote({ + name: 'procurementsByMethodRaw', + url: procurementsByMethodUrl, + params: PEFilters, +}); + +export const procurementsByMethodData = PEState.mapping({ + name: 'procurementsByMethodData', + deps: [procurementsByMethodRaw], + mapper: data => data.map( + datum => ({ + status: datum.tenderStatus, + count: datum.count, + }) + ).sort((a, b) => b.count - a.count) +}); + +const procurementsByStatusUrl = PEState.input({ + name: 'procurementsByStatusUrl', + initial: `${API_ROOT}/procurementsByTenderStatus`, +}); + +const procurementsByStatusRaw = PEState.remote({ + name: 'procurementsByStatusRaw', + url: procurementsByStatusUrl, + params: PEFilters, +}); + +export const procurementsByStatusData = PEState.mapping({ + name: 'procurementsByStatusData', + deps: [procurementsByStatusRaw], + mapper: data => data.map( + datum => ({ + status: datum.tenderStatus, + count: datum.count, + }) + ).sort((a, b) => b.count - a.count) +}); + +export const maxCommonDataLength = PEState.mapping({ + name: 'maxCommonDataLength', + deps: [procurementsByStatusData, procurementsByMethodData], + mapper: (a, b) => Math.min( + 5, + Math.max(a.length, b.length) + ) +}); + +export const max2ndRowCommonDataLength = PEState.mapping({ + name: 'max2ndRowCommonDataLength', + deps: [winsAndFlagsData, PEFlaggedNrData], + mapper: (a, b) => Math.min( + 5, + Math.max(a.length, b.length) + ), +}); diff --git a/ui/oce/corruption-risk/procuring-entities/single/style.less b/ui/oce/corruption-risk/procuring-entities/single/style.less new file mode 100644 index 000000000..c08ea8afb --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/style.less @@ -0,0 +1,28 @@ +.pe-page { + font-family: 'Montserrat'; + section.info .info-table { + font-family: 'Source Sans Pro'; + color: #354052; + .flags { + .count { + color: #ce4747; + font-weight: 600; + } + + small { + margin-left: 0; + } + } + } + .flag-analysis { + h2 { + margin-top: 40px; + } + } + + h2 { + font-family: 'Source Sans Pro'; + font-weight: 600; + margin-top: -20px; + } +} diff --git a/ui/oce/corruption-risk/procuring-entities/single/table/index.jsx b/ui/oce/corruption-risk/procuring-entities/single/table/index.jsx new file mode 100644 index 000000000..574f1a497 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/table/index.jsx @@ -0,0 +1,105 @@ +import translatable from '../../../../translatable'; +import BootstrapTableWrapper from '../../../archive/bootstrap-table-wrapper'; +import { procurementsData, page, pageSize, procurementsCount } from './state'; +import { mkContractLink } from '../../../tools'; + +const NAME = 'PEProcurementsComponent'; + +class Table extends translatable(React.PureComponent) { + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.data = []; + } + + componentDidMount() { + procurementsData.addListener(NAME, () => this.updateBindings()); + page.addListener(NAME, () => this.updateBindings()); + pageSize.addListener(NAME, () => this.updateBindings()); + procurementsCount.addListener(NAME, () => this.updateBindings()); + } + + updateBindings() { + Promise.all([ + procurementsData.getState(NAME), + page.getState(NAME), + pageSize.getState(NAME), + procurementsCount.getState(NAME), + ]).then(([data, page, pageSize, procurementsCount]) => { + this.setState({ + data, + page, + pageSize, + count: procurementsCount, + }) + }) + } + + componentWillUnmount() { + procurementsData.removeListener(NAME); + page.removeListener(NAME); + pageSize.removeListener(NAME); + procurementsCount.removeListener(NAME); + } + + formatFlags(data) { + return ( +
+ {data.map(indicator => ( +
+ ● {this.t(`crd:indicators:${indicator}:name`)} +
+ ))} +
+ ); + } + + render() { + const { data, count } = this.state; + const { navigate } = this.props; + + return ( + page.assign(NAME, newPage)} + onSizePerPageList={newPageSize => pageSize.assign(NAME, newPageSize)} + count={count} + columns={[{ + title: 'Tender name', + dataField: 'name', + width: '20%', + dataFormat: mkContractLink(navigate), + }, { + title: 'OCID', + dataField: 'id', + dataFormat: mkContractLink(navigate), + }, { + title: 'Award status', + dataField: 'awardStatus', + }, { + title: 'Tender amount', + dataField: 'tenderAmount', + }, { + title: this.t('crd:contracts:list:awardAmount'), + dataField: 'awardAmount', + }, { + title: 'Number of bidders', + dataField: 'nrBidders', + }, { + title: 'Number of flags', + dataField: 'nrFlags', + }, { + title: this.t('crd:supplier:table:flagName'), + dataField: 'flags', + dataFormat: this.formatFlags.bind(this), + width: '20%', + }]} + /> + ); + } +} + +export default Table; diff --git a/ui/oce/corruption-risk/procuring-entities/single/table/state.jsx b/ui/oce/corruption-risk/procuring-entities/single/table/state.jsx new file mode 100644 index 000000000..da5338729 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/single/table/state.jsx @@ -0,0 +1,85 @@ +import { PEState, PEFilters } from '../state'; +import { API_ROOT } from '../../../../state/oce-state'; + +export const PETableState = PEState.substate({ + name: 'PETableState', +}); + +const procurementsEP = PEState.input({ + name: 'procurementsEP', + initial: `${API_ROOT}/flaggedRelease/all`, +}); + +export const page = PEState.input({ + name: 'page', + initial: 1, +}); + +export const pageSize = PEState.input({ + name: 'pageSize', + initial: 20, +}); + +const filters = PEState.mapping({ + name: 'filters', + deps: [PEFilters, page, pageSize], + mapper: (PEFilters, page, pageSize) => + PEFilters + .set('pageSize', pageSize) + .set('pageNumber', page - 1) +}); + +const procurementsRaw = PEState.remote({ + name: 'procurementsRaw', + url: procurementsEP, + params: filters, +}); + +const findActiveAward = awards => + awards.find( + award => award.status === 'active' + ); + +function getAwardAmount(awards) { + const award = findActiveAward(awards); + if (!award) return 0; + const { value } = award; + return `${value.amount} ${value.currency}`; +} + +function getTenderAmount(datum) { + try { + return `${datum.tender.value.amount} ${datum.tender.value.currency}`; + } catch(whatever) { + return 0; + } +} + +export const procurementsData = PEState.mapping({ + name: 'procurementsData', + deps: [procurementsRaw], + mapper: raw => + raw.map(datum => { + return { + id: datum.ocid, + name: datum.tender.title || 'N/A', + awardStatus: getAwardAmount(datum.awards) ? 'active' : 'unsuccessful', + tenderAmount: getTenderAmount(datum), + awardAmount: getAwardAmount(datum.awards), + nrBidders: datum.tender.numberOfTenderers || 0, + nrFlags: datum.flags.totalFlagged, + flags: Object.keys(datum.flags).filter(key => datum.flags[key].value), + } + }) +}) + +const procurementsCountEP = PEState.input({ + name: 'procurementsCountEP', + initial: `${API_ROOT}/flaggedRelease/count`, +}); + +export const procurementsCount = PEState.remote({ + name: 'procurementsCount', + url: procurementsCountEP, + params: PEFilters, +}); diff --git a/ui/oce/corruption-risk/procuring-entities/state.jsx b/ui/oce/corruption-risk/procuring-entities/state.jsx new file mode 100644 index 000000000..6351ee0d4 --- /dev/null +++ b/ui/oce/corruption-risk/procuring-entities/state.jsx @@ -0,0 +1,62 @@ +import { Set } from 'immutable'; +import { CRD, datefulFilters, API_ROOT } from '../../state/oce-state'; + +export const PEIds = CRD.input({ + name: 'PEIds' +}); + +export const PEsFilters = CRD.mapping({ + name: 'PEsFilters', + deps: [datefulFilters, PEIds], + mapper: (filters, PEId) => filters.update( + 'procuringEntityId', + Set(), + PEIds => PEIds.add(PEId) + ) +}); + +const TendersCountEP = CRD.input({ + name: 'TendersCountEP', + initial: `${API_ROOT}/procuringEntitiesTendersCount`, +}); + +const AwardsCountEP = CRD.input({ + name: 'AwardsCountEP', + initial: `${API_ROOT}/procuringEntitiesAwardsCount`, +}); + +const TendersCountRaw = CRD.remote({ + name: 'TendersCountRaw', + url: TendersCountEP, + params: PEsFilters, +}); + +export const TendersCount = CRD.mapping({ + name: 'TendersCount', + deps: [TendersCountRaw], + mapper: raw => { + const result = {}; + raw.forEach(({ _id, tenderCount }) => { + result[_id] = tenderCount + }); + return result; + }, +}); + +const AwardsCountRaw = CRD.remote({ + name: 'AwardsCountRaw', + url: AwardsCountEP, + params: PEsFilters, +}); + +export const AwardsCount = CRD.mapping({ + name: 'AwardsCount', + deps: [AwardsCountRaw], + mapper: raw => { + const result = {}; + raw.forEach(({ _id, awardCount }) => { + result[_id] = awardCount + }); + return result; + }, +}) diff --git a/ui/oce/corruption-risk/sidebar.jsx b/ui/oce/corruption-risk/sidebar.jsx new file mode 100644 index 000000000..50a6bcbd8 --- /dev/null +++ b/ui/oce/corruption-risk/sidebar.jsx @@ -0,0 +1,128 @@ +import ReactDOM from 'react-dom'; +import cn from 'classnames'; +import { Map } from 'immutable'; +import translatable from '../translatable'; +import TotalFlags from './total-flags'; +import { CORRUPTION_TYPES } from './constants'; + +// eslint-disable-next-line no-undef +class Sidebar extends translatable(React.PureComponent) { + componentDidMount() { + const el = ReactDOM.findDOMNode(this); + const scrollTarget = el.querySelector('div'); + const offsetTop = el.getBoundingClientRect().top; + + window.addEventListener('wheel', e => { + let margin = parseInt(scrollTarget.style.marginTop); + if (isNaN(margin)) margin = 0; + if (e.deltaY > 0) { + margin -= 40; + } else if (window.scrollY === 0) { + margin += 40; + } + + const newMargin = Math.min( + 0, + Math.max( + margin, + window.innerHeight - scrollTarget.offsetHeight - offsetTop + ) + ); + scrollTarget.style.marginTop = `${newMargin}px`; + }); + } + + render() { + const { page, indicatorTypesMapping, filters, years, monthly, months, navigate, translations, + data, requestNewData, route, allYears } = this.props; + + return ( + + ); + } +} + +export default Sidebar; diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index 7170f70b2..6997af175 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -1,11 +1,18 @@ +@import url('https://fonts.googleapis.com/css?family=Montserrat:400,600,700|Source+Sans+Pro:400,600'); + @headerHeight: 64px; +@gutterRight: 80pt; +@gutterRightMd: 60pt; + .introduction { font-size: 16px; margin-bottom: 20px; margin-top: 10px; } -.dashboard-corruption-risk{ + +.dashboard-corruption-risk { &>header{ + font-family: 'Montserrat'; background: #144361; &.branding { height: 54px; @@ -15,8 +22,11 @@ float: left; } - .logo-wrapper>img{ - height: 54px; + img.logo { + width: 45px; + height: 45px; + margin-left: 15px; + margin-top: 5px; } .dash-switcher-wrapper{ @@ -27,8 +37,9 @@ padding: 5px; &.corruption-dash-title{ color: #9fa9ba; - font-size: 20px; - margin-top: 10px; + font-size: 18px; + font-weight: 600; + margin-top: 12px; } .glyphicon{ font-size: .53em; @@ -47,30 +58,44 @@ } } - .login-wrapper{ - padding: 10px 0 15px 15px; - text-align: right; - button{ - width: 150px; - background: #35ae46; + .header-right { + display: flex; + align-items: center; + justify-content: flex-end; + .login-wrapper{ + margin-right: @gutterRight; + @media (max-width: 1200px) { + margin-right: @gutterRightMd; + } + margin-left: 10px; + button { + width: 150px; + background: #35ae46; + } } } } &>.filters-bar{ + font-family: 'Montserrat'; background: #c3e6fc; top: @headerHeight - 15; height: @headerHeight - 9; left: 0; right: 0; position: fixed; - z-index: 2; + z-index: 10; .title{ text-transform: uppercase; float: left; - font-size: 14px; + font-size: 12px; font-weight: bold; padding: 20px 30px 20px 8px; + margin-left: 60px; + margin-right: 35px; + @media (max-width: 1024px) { + margin-right: 5px; + } @media (max-width: 969px) { padding: 0 5px; } @@ -96,10 +121,14 @@ margin-top: 3px; } + .box-title { + font-size: 12px; + } + &.open{ position: relative; .box-title{ - font-weight: bold; + font-weight: 600; } .dropdown{ display: block; @@ -143,10 +172,14 @@ } } .download{ - padding: 14px 0; + padding: 14px 15px; text-align: right; - button{ + button { padding: 5px 25px; + margin-right: @gutterRight; + @media (max-width: 1200px) { + margin-right: @gutterRightMd; + } }; } @@ -171,26 +204,11 @@ } } - @media (max-height: 650px) { - position: relative; - aside{ - .crd-description-text p{ - display: none; - } - section[role=navigation]{ - margin-top: 0; - } - .crd-description-text:hover p{ - display: block; - } - } - } - aside{ @leftPadding: 30px; bottom:0; - background: #fafafa; - border-right: 1px solid #e6eaee; + background: transparent; + border-right: none; height: 100%; padding-left: @leftPadding; padding-top: 10px; @@ -200,27 +218,34 @@ font-size: 80%; line-height:128%; } + i{ color: #34ac45; float: right; } + .total-flags{ - background: #eff9ff; - margin-left: -30px; - position: absolute; - width: 100%; - height: 100%; + background: #fafafa; + margin: 15px -15px 0 -30px; + padding-bottom: 50px; @infoWidth: 250px; - .total-flags-counter{ - width: @infoWidth; - margin: 20px auto 0 auto; + border-radius: 5px; + border: 1px solid #e7ebef; + border-left-style: none; + .counter{ + padding: 10px 20px 15px 20px; + &:first-child { + border-bottom: 1px solid #e7ebef; + } .text{ - float: left; - font-size: 1.5em; - padding-top: (2em - 1.5em) / 2; + font-size: 0.95vw; + padding-top: .5em; } .count{ - font-size: 2em; + float: right; + font-size: 1.5vw; + font-weight: bold; + color: #144361; } } .crd-legend{ @@ -258,18 +283,16 @@ } } } - .crd-overview-link{ - cursor: pointer; - &:hover { - color: #35ae46; - } - } + [role=navigation]{ margin-left: -@leftPadding; - margin-top: 20px; + margin-top: -1px; + border-right: 1px solid #e7ebef; + border-bottom-right-radius: 5px; a{ border-bottom: 1px solid #e6eaee; color: #000000; + background: #fafafa; cursor: pointer; display: block; font-size: 0.9em; @@ -282,19 +305,69 @@ &:first-child{ border-top: 1px solid #e6eaee; } - &.active, &:hover{ - background: #ffffff; - color: #2b9ff6; - } + .count{ color: #a9b2c0; display: inline-block; margin-left: 10px; } + + &.archive-link { + background: #ecf7ff; + } + + &.contracts-link{ + background: #dbf0ff; + } + img{ display: inline-block; margin-right: 10px; width: 32px; + &.white { + display: none; + } + } + + &.active, &:hover{ + background: #35ae46; + color: white; + .count { + color: white; + } + + img.white { + display: inline-block; + } + + img.blue { + display: none; + } + } + } + + .crd-description{ + padding: 8px 8px 8px 25px; + display: none; + } + + .crd-description-link { + &.active { + &+.crd-description { + display: block; + } + } + + &:hover, &.active{ + .glyphicon { + color: white; + } + } + + .glyphicon { + color: #34ac45; + padding: 5px 15px; + font-size: 1.5em; } } } @@ -306,20 +379,31 @@ .table-striped>tbody>tr:nth-of-type(even){ background: #f5f8fa; } + .content{ @arrowSize: 8px; margin-top: 89px; - margin-bottom: auto; - padding: 20px 40px 0 30px; + margin-bottom: 80px; + padding: 30px @gutterRight 0 50px; + @media (max-width: 1200px) { + padding: 30px @gutterRightMd 0 50px; + } - .crd-popup{ - @popupColor: #222c3c; + .crd-popup { @popupWidth: 400px;//if you're changing this you need to also change POPUP_WIDTH in constants.es6 position: absolute; top: 0; left: 0; width: @popupWidth; height: 150px; + + hr { + width: 50%; + } + } + + .crosstab-box, .crd-popup{ + @popupColor: #222c3c; z-index: 1; background: @popupColor; padding: 15px 5px; @@ -333,11 +417,12 @@ .info{ color: white; } - hr{ - width: 50%; + + hr { opacity: .1; margin: 5px auto; } + .arrow{ background: @popupColor; position: absolute; @@ -350,33 +435,18 @@ } } - .chart-corruption-types{ - position: relative; - .hovertext{ - opacity: 0; + .crosstab-box { + padding: 15px; + hr { + width: 100%; } } - .table-top-flagged-contracts{ - margin-top: 20px; - th, td{ - font-weight: normal; - padding: 8px; - } - th{ - border-bottom: none; - color: #7f8fa4; - } - td{ - border-bottom: 1px solid #e7e9ed; - border-top: none; - } - } - .table-project-table, .table-top-flagged-contracts{ - font-size: 12px; - td{ - width: 11%; + .chart-corruption-types{ + position: relative; + .hovertext{ + opacity: 0; } } @@ -430,12 +500,40 @@ } } + td.hoverable:hover, td.selectable.selected { + box-shadow: inset 0px 0px 0px 5px #ffd307; + } + + .react-bs-table { + th{ + color: #a1adbc; + } + td{ + font-weight: 600; + font-size: .9em; + } + } + + .react-bs-container-body { + &, td.hoverable { + overflow: visible; + } + + td.hoverable { + padding: 0; + &>div { + padding: 8px; + } + } + } + td.hoverable{ position: relative; .crd-popup{ padding: 15px; height: auto; display: none; + z-index: 10; .and{ text-transform: uppercase; color: #387ab2; @@ -449,7 +547,6 @@ } &:hover { - box-shadow: inset 0px 0px 0px 5px #ffd307; .crd-popup{ display: block; top: auto; @@ -462,10 +559,11 @@ &.popup-left:hover .crd-popup{ left: auto; right: 100%; - top: -100%; + top: 50%; bottom: auto; margin-left: 0; margin-right: @arrowSize; + transform: translateY(-50%); } } @@ -482,10 +580,15 @@ .oce-3-line-text{ overflow: hidden; - max-height: 3.9em; + max-height: 4em; + display: block; + &:hover { + overflow: visible; + max-height: none; + } } - .crd-landing-popup{ + .crd-fullscreen-popup{ @popupWidth: 75%; position: fixed; background: #f9fff6; @@ -540,7 +643,7 @@ } } - .crd-landing-popup-overlay{ + .crd-fullscreen-popup-overlay{ position: fixed; top: 0; right: 0; @@ -549,4 +652,134 @@ z-index: 9998; background: rgba(0, 0, 0, .7); } + + .page-individual-indicator { + .tender-date { + width: 155px; + } + } +} + +.custom-popup-container { + position: relative; + .crd-popup { + display: none; + } + &:hover .crd-popup { + display: block; + } +} + +.contract-page, .supplier-page, .pe-page { + dl { + margin-bottom: 0; + dt { + font-size: .7em; + font-weight: normal; + } + dd { + font-size: 1.3em; + p { + margin-bottom: 0; + } + } + + &.smaller { + dd { + font-size: 1em; + } + } + } + + table { + &.join-bottom { + margin-bottom: 0; + &, td { + border-bottom: none; + } + } + + &.info-table { + th, td{ + width: 33%; + } + } + } + + section h2 { + color: #ce4747; + } + + .flag-icon { + width: 32px; + margin-top: -5px; + } + + section.info { + margin-bottom: 40px; + + &>.row{ + margin-left: 0; + margin-right: 0; + &>*{ + padding-left: 0; + } + } + + .flags { + font-size: 2em; + small { + margin-left: 45px; + font-size: .6em; + display: block; + } + } + } +} + +.oce-chart { + position: relative; + @msgWidth: 200px; + @msgHeight: 70px; + .message { + position: absolute; + top: 50%; + left: 50%; + margin-top: -@msgHeight / 2; + margin-left: -@msgWidth / 2; + width: @msgWidth; + height: @msgHeight; + border: 1px solid #333; + display: flex; + justify-content: center; + align-items: center; + } + + .recharts-label, .recharts-cartesian-axis-ticks { + fill: #adafb2; + font-size: 12px; + font-family: Montserrat; + font-weight: 600; + } + + .recharts-legend-item-text { + font-family: Source Sans Pro; + font-size: 16px; + font-weight: 600; + color: #767676; + } + + & + .title { + font-family: 'Source Sans Pro'; + font-size: 18px; + font-weight: 600; + text-align: left; + color: #354052; + text-indent: -5px; + padding-right: 40px; + button { + float: right; + margin-right: -40px; + } + } } diff --git a/ui/oce/corruption-risk/suppliers/index.jsx b/ui/oce/corruption-risk/suppliers/index.jsx new file mode 100644 index 000000000..38abc4334 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/index.jsx @@ -0,0 +1,160 @@ +import URI from 'urijs'; +import { List, Map } from 'immutable'; +import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; +import CRDPage from '../page'; +import PaginatedTable from '../paginated-table'; +import Archive from '../archive'; +import { wireProps } from '../tools'; +import { fetchEP, pluckImm, cacheFn } from '../../tools'; +import BackendDateFilterable from '../backend-date-filterable'; + +export const mkLink = navigate => (content, { id }) => ( + navigate('supplier', id)} + > + {content} + +); + +class SList extends PaginatedTable { + getCustomEP() { + const { searchQuery } = this.props; + const eps = super.getCustomEP(); + return searchQuery ? + eps.map(ep => ep.addSearch('text', searchQuery)) : + eps; + } + + componentDidUpdate(prevProps, prevState) { + const propsChanged = ['filters', 'searchQuery'].some(key => this.props[key] !== prevProps[key]); + if (propsChanged) { + this.fetch(); + } else { + super.componentDidUpdate(prevProps, prevState); + } + } + + render() { + const { data, navigate } = this.props; + + const count = data.get('count', 0); + + const { pageSize, page } = this.state; + + const jsData = data.get('data', List()).map((supplier) => { + return { + id: supplier.get('supplierId'), + name: supplier.get('supplierName'), + wins: supplier.get('wins'), + winAmount: supplier.get('winAmount'), + losses: supplier.get('losses'), + flags: supplier.get('countFlags'), + } + }).toJS(); + + return ( + this.setState({ page: newPage }), + sizePerPage: pageSize, + sizePerPageList: [20, 50, 100, 200].map(value => ({ text: value, value })), + onSizePerPageList: newPageSize => this.setState({ pageSize: newPageSize }), + paginationPosition: 'both', + }} + > + + {this.t('crd:suppliers:name')} + + + {this.t('crd:suppliers:ID')} + + + {this.t('crd:suppliers:wins')} + + + {this.t('crd:suppliers:losses')} + + + {this.t('crd:suppliers:totalWon')} + + + {this.t('crd:suppliers:nrFlags')} + + + ); + } +} + +class Suppliers extends CRDPage { + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.winLossFlagInfo = Map(); + this.injectWinLossData = cacheFn((data, winLossFlagInfo) => { + return data.update('data', List(), list => list.map(supplier => { + const id = supplier.get('supplierId'); + if (!winLossFlagInfo.has(id)) return supplier; + const info = winLossFlagInfo.get(id); + return supplier + .set('wins', info.won.count) + .set('winAmount', info.won.totalAmount) + .set('losses', info.lostCount) + .set('flags', info.applied.countFlags) + })) + }); + } + + onNewDataRequested(path, newData) { + const supplierIds = newData.get('data').map(pluckImm('supplierId')); + this.setState({ winLossFlagInfo: Map() }); + fetchEP(new URI('/api/procurementsWonLost').addSearch({ + bidderId: supplierIds.toJS(), + })).then(result => { + this.setState({ + winLossFlagInfo: Map(result.map(datum => [ + datum.applied._id, + datum + ])) + }); + }); + this.props.requestNewData( + path, + newData.set('count', newData.getIn(['count', 0, 'count'], 0)) + ); + } + + render() { + const { navigate, searchQuery, doSearch, data } = this.props; + const { winLossFlagInfo } = this.state; + return ( + + + + ); + } +} + +export default Suppliers; diff --git a/ui/oce/corruption-risk/suppliers/single/bars/flagged-nr/index.jsx b/ui/oce/corruption-risk/suppliers/single/bars/flagged-nr/index.jsx new file mode 100644 index 000000000..0b39ce35c --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/bars/flagged-nr/index.jsx @@ -0,0 +1,174 @@ +import { ResponsiveContainer, BarChart, XAxis, YAxis, Legend, Bar, LabelList, Tooltip } from 'recharts'; +import translatable from '../../../../../translatable'; +import { renderTopLeftLabel } from '../../../../archive/tools'; +import Popup from './popup'; + +const corruptionTypeColors = { + FRAUD: '#299df4', + RIGGING: '#3372b2', + COLLUSION: '#fbc42c' +} + +function mkGradient(id, colors) { + const stops = []; + const step = 100 / colors.length; + colors.forEach((color, index) => { + stops.push({ + color, + offset: step * index + 1, + }); + stops.push({ + color, + offset: step * (index + 1) - 1, + }); + }); + + return ( + + {stops.map(({color, offset}) => + + )} + + ) +} + +class TaggedBar extends translatable(Bar) { + maybeGetGradients(types) { + return mkGradient( + this.getGradientId(types), + types.map(type => corruptionTypeColors[type]) + ) + } + + getGradientId(types) { + return `gradient_${types.join('_')}` + } + + getFill(types) { + if (types.length === 1) { + const [type] = types; + return corruptionTypeColors[type]; + } else { + return `url(#${this.getGradientId(types)})` + } + } + + renderRectangle(option, props) { + const types = props.types; + return ( + + {this.maybeGetGradients(types)} + {super.renderRectangle(option, { + ...props, + fill: this.getFill(types), + })} + + ) + } +} + +export default class FlaggedNr extends translatable(React.PureComponent) { + constructor(props) { + super(props); + this.state = this.state || {}; + this.state.data = []; + this.state.length = 5; + } + + componentDidMount() { + const { zoomed, data, length } = this.props; + const name = zoomed ? 'ZoomedFlaggedNrChart' : 'FlaggedNrChart'; + data.addListener(name, () => { + data.getState(name).then(data => { + this.setState({ + data + }) + }) + }); + length.addListener(name, () => { + length.getState(name).then(length => this.setState({ length })) + }); + } + + componentWillUnmount() { + const { zoomed, data, length } = this.props; + const name = zoomed ? 'ZoomedFlaggedNrChart' : 'FlaggedNrChart'; + data.removeListener(name); + length.removeListener(name); + } + + render() { + const { zoomed, translations } = this.props; + let { data, length } = this.state; + let height = 350; + if (zoomed) { + height = Math.max(height, data.length * 50); + } else { + data = data.slice(0, length); + if (data.length < length) { + for(let counter = data.length; counter < length; counter++) { + data.unshift({ types: [] }); + } + } + + height = Math.max(length * 70, 200); + } + + const corruptionTypes = new Set(); + data.forEach( + datum => datum.types.forEach( + type => corruptionTypes.add(type) + ) + ); + + const legendPayload = [...corruptionTypes].map( + corruptionType => ({ + value: this.t(`crd:corruptionType:${corruptionType}:name`), + type: 'square', + color: corruptionTypeColors[corruptionType], + }) + ); + return ( +
+ + + + + } translations={translations} cursor={false} /> + + + this.t(`crd:indicators:${indicatorId}:name`)} + dataKey="indicatorId" + position="insideTopLeft" + content={renderTopLeftLabel} + /> + + + +
+ ) + } +} + diff --git a/ui/oce/corruption-risk/suppliers/single/bars/flagged-nr/popup.jsx b/ui/oce/corruption-risk/suppliers/single/bars/flagged-nr/popup.jsx new file mode 100644 index 000000000..f17cca610 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/bars/flagged-nr/popup.jsx @@ -0,0 +1,39 @@ +import translatable from '../../../../../translatable'; + +class Popup extends translatable(React.PureComponent) { + render() { + const { coordinate, active, viewBox, payload } = this.props; + if (!active || !payload[0]) return null; + + const { count, indicatorId } = payload[0].payload; + + let POPUP_HEIGHT = 55; + + const style = { + left: 0, + top: coordinate.y - POPUP_HEIGHT - viewBox.top - 4, + width: 350, + height: POPUP_HEIGHT, + } + + const label = count === 1 ? + this.t('crd:supplier:flaggedNr:popup:sg') : + this.t('crd:supplier:flaggedNr:popup:pl'); + + const indicatorName = this.t(`crd:indicators:${indicatorId}:name`); + + return ( +
+
+ {label.replace('$#$', count).replace('$#$', indicatorName)} +
+
+
+ ); + } +} + +export default Popup; diff --git a/ui/oce/corruption-risk/suppliers/single/bars/wins-and-flags/index.jsx b/ui/oce/corruption-risk/suppliers/single/bars/wins-and-flags/index.jsx new file mode 100644 index 000000000..eab1a166b --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/bars/wins-and-flags/index.jsx @@ -0,0 +1,101 @@ +import { BarChart, Bar, XAxis, YAxis, LabelList, ResponsiveContainer, Legend, Tooltip, Text } from 'recharts'; +import translatable from '../../../../../translatable'; +import Popup from './popup'; +import { renderTopLeftLabel } from '../../../../archive/tools'; + +class WinsAndFlags extends translatable(React.PureComponent) { + constructor(props) { + super(props); + this.state = this.state || {}; + this.state.data = []; + this.state.length = 5; + } + + componentDidMount() { + const { zoomed, data, length } = this.props; + const name = zoomed ? 'ZoomedWinsAndFlagsChart' : 'WinsAndFlagsChart'; + data.addListener(name, () => { + data.getState(name).then(data => { + this.setState({ + data + }) + }) + }); + length.addListener(name, () => { + length.getState(name).then(length => { this.setState({ length }) }); + }) + } + + componentWillUnmount() { + const { zoomed, data, length } = this.props; + const name = zoomed ? 'ZoomedWinsAndFlagsChart' : 'WinsAndFlagsChart'; + data.removeListener(name); + length.removeListener(name); + } + + render() { + const { translations, zoomed } = this.props; + let { data, length } = this.state; + + let height = 350; + let slicedData = data; + if (zoomed) { + height = Math.max(height, data.length * 50); + } else { + slicedData = data.slice(0, length); + if (slicedData.length < length) { + for(let counter = slicedData.length; counter < length; counter++) { + slicedData.unshift({}); + } + } + + height = Math.max(length * 70, 200); + } + + return ( +
+ + + + + } translations={translations} cursor={false}/> + + + + + + + + {!data.length &&
No data
} +
+ ) + } +} + +export default WinsAndFlags; diff --git a/ui/oce/corruption-risk/suppliers/single/bars/wins-and-flags/popup.jsx b/ui/oce/corruption-risk/suppliers/single/bars/wins-and-flags/popup.jsx new file mode 100644 index 000000000..38fc188fa --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/bars/wins-and-flags/popup.jsx @@ -0,0 +1,50 @@ +import translatable from '../../../../../translatable'; + +class Popup extends translatable(React.PureComponent) { + render() { + const { coordinate, active, viewBox, payload } = this.props; + if (!active || !payload[0]) return null; + + const { name, wins, flags } = payload[0].payload; + + let POPUP_HEIGHT = 70; + if (name.length > 100) { + POPUP_HEIGHT = 140; + } else if (name.length > 70 ) { + POPUP_HEIGHT = 110; + } else if (name.length > 40 ) { + POPUP_HEIGHT = 90; + } + + const style = { + left: 0, + top: coordinate.y - POPUP_HEIGHT - viewBox.top - 4, + width: 300, + height: POPUP_HEIGHT, + } + + const winLabel = wins === 1 ? + this.t('crd:supplier:win:sg') : + this.t('crd:supplier:win:pl'); + + const flagLabel = flags === 1 ? + this.t('crd:contracts:baseInfo:flag:sg') : + this.t('crd:contracts:baseInfo:flag:pl'); + + return ( +
+
+ {name} +
+ {wins} {winLabel}, {flags} {flagLabel} +
+
+
+ ); + } +} + +export default Popup; diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/amount-lost-vs-won.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/amount-lost-vs-won.jsx new file mode 100644 index 000000000..47b8232a0 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/donuts/amount-lost-vs-won.jsx @@ -0,0 +1,65 @@ +import { pluck } from '../../../../tools'; +import Donut from '../../../donut'; +import translatable from '../../../../translatable'; + +class CenterText extends translatable(React.PureComponent) { + format(number) { + const formatted = this.props.styling.charts.hoverFormatter(number) || ''; + return [ + formatted.slice(0, -1), + {formatted.slice(-1)} + ]; + } + + render() { + const { data } = this.props; + const [fst, snd] = data.map(pluck('value')); + return ( +
+
+ ${this.format(fst)} +
+ ${this.format(snd)} {this.t('crd:supplier:amountLostVsWon:Lost')} +
+
+
+ ); + } +} + +class AmountWonVsLost extends translatable(React.Component) { + transformNewData(path, data) { + const { styling } = this.props; + const won = data.getIn([0, 'won', 'totalAmount']); + const lost = data.getIn([0, 'lostAmount']); + + this.props.requestNewData(path, [{ + color: '#2e833a', + label: this.t('crd:supplier:amountLostVsWon:won') + .replace('$#$', styling.charts.hoverFormatter(won)), + value: won + }, { + color: '#72c47e', + label: this.t('crd:supplier:amountLostVsWon:lost') + .replace('$#$', styling.charts.hoverFormatter(lost)), + value: lost + }]); + } + + render() { + const { data } = this.props; + return ( + + ); + } +} + +export default AmountWonVsLost; diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx new file mode 100644 index 000000000..121b73a35 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx @@ -0,0 +1,55 @@ +import { List } from 'immutable'; +import Donut from '../../../donut'; +import translatable from '../../../../translatable'; +import { pluck } from '../../../../tools'; + +class CenterText extends React.PureComponent { + render() { + const { data } = this.props; + let style = {}; + const label = data.map(pluck('value')).join('/'); + if (label.length > 9) { + style = { fontSize: '2vw' } + } + + return ( +
+ {data.map(({ color, value }) => + {value} + )} +
+ ); + } +} + +const COLORS = ['#fbc42c', '#3372b2', '#30a0f5']; + +class TotalFlags extends translatable(React.PureComponent) { + render() { + const data = (this.props.data || List()).map((datum, index) => { + const value = datum.get('indicatorCount'); + const indicatorName = this.t(`crd:corruptionType:${datum.get('type')}:name`); + const label = value === 1 ? + this.t('crd:supplier:nrFlags:label:sg') : + this.t('crd:supplier:nrFlags:label:pl'); + + return { + color: COLORS[index], + label: label.replace('$#$', value).replace('$#$', indicatorName), + value: datum.get('indicatorCount'), + }; + }).toJS(); + return ( + + ); + } +} + +export default TotalFlags; diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx new file mode 100644 index 000000000..0b3faae77 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx @@ -0,0 +1,59 @@ +import { fromJS } from 'immutable'; +import { pluck } from '../../../../tools'; +import Donut from '../../../donut'; +import translatable from '../../../../translatable'; + +class CenterText extends React.Component { + render() { + const { data } = this.props; + if (!data) return null; + const [won, lost] = data.map(pluck('value')); + const sum = won + lost; + const percent = (won / sum) * 100; + return ( +
+
+ {won} +
+ of {sum} ({Math.trunc(percent)}%) +
+
+
+ ); + } +} + +class NrWonVsLost extends translatable(React.PureComponent) { + transformNewData(path, data) { + const won = data.getIn([0, 'won', 'count']); + const lost = data.getIn([0, 'lostCount']); + const sum = won + lost; + const wonPercent = (won / sum * 100).toFixed(2); + const lostPercent = (lost / sum * 100).toFixed(2); + this.props.requestNewData(path, [{ + color: '#165781', + label: this.t('crd:supplier:nrLostVsWon:won').replace('$#$', won).replace('$#$', wonPercent), + value: won + }, { + color: '#5fa0c9', + label: this.t('crd:supplier:nrLostVsWon:lost').replace('$#$', lost).replace('$#$', lostPercent), + value: lost + }]); + } + + render() { + return ( + + ); + } +} + +export default NrWonVsLost; diff --git a/ui/oce/corruption-risk/suppliers/single/index.jsx b/ui/oce/corruption-risk/suppliers/single/index.jsx new file mode 100644 index 000000000..041015598 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/index.jsx @@ -0,0 +1,332 @@ +import { Map, Set, List } from 'immutable'; +import TopSearch from '../../top-search'; +import translatable from '../../../translatable'; +import Visualization from '../../../visualization'; +import CRDPage from '../../page'; +import { wireProps } from '../../tools'; +import NrLostVsWon from './donuts/nr-lost-vs-won'; +import AmountLostVsWon from './donuts/amount-lost-vs-won'; +import NrFlags from './donuts/nr-flags'; +import styles from './style.less'; +import { cacheFn, pluckImm } from '../../../tools'; +import Zoomable from '../../zoomable'; +import Crosstab from '../../clickable-crosstab'; +import { CORRUPTION_TYPES } from '../../constants'; +import FlaggedNr from './bars/flagged-nr'; +import BackendDateFilterable from '../../backend-date-filterable'; +import { supplierId, supplierFlaggedNrData, winsAndFlagsData, maxCommonDataLength } from './state'; +import WinsAndFlags from './bars/wins-and-flags/index'; +import SupplierTable from './table'; +import TitleBelow from '../../archive/title-below'; + +class CrosstabExplanation extends translatable(React.PureComponent) { + render() { + const { nrFlags, corruptionType } = this.props; + return ( +

+ {this.t('crd:supplier:crosstabs:explanation') + .replace('$#$', nrFlags) + .replace('$#$', this.t(`crd:corruptionType:${corruptionType}:pageTitle`))} +

+ ); + } +} + +class Info extends translatable(Visualization) { + getCustomEP() { + const { id } = this.props; + return [ + `ocds/organization/supplier/id/${id}`, + `totalFlags?supplierId=${id}`, + `ocds/release/count?supplierId=${id}`, + ]; + } + + transform([info, _totalFlags, totalContracts]) { + let totalFlags = 0; + try { + totalFlags = _totalFlags[0].flaggedCount; + } catch(e) { + console.log('Total flags fetching failed', e); + } + return { + info, + totalFlags, + totalContracts, + }; + } + + render() { + const { data } = this.props; + + if (!data) return null; + + const info = data.get('info'); + const flagCount = data.get('totalFlags'); + const contractCount = data.get('totalContracts'); + + const address = info.get('address'); + const contact = info.get('contactPoint'); + return ( +
+ + + + + + + + +
+
+
{this.t('crd:supplier:ID')}
+
{info.get('id')}
+
+
+
+
{this.t('crd:supplier:name')}
+
{info.get('name')}
+
+
+ Flag icon +   + {flagCount} +   + {this.t(flagCount === 1 ? + 'crd:contracts:baseInfo:flag:sg' : + 'crd:contracts:baseInfo:flag:pl')} +
+ + ( + {contractCount} +   + {this.t(contractCount === 1 ? + 'crd:supplier:contract:sg' : + 'crd:supplier:contract:pl')} + ) + +
+ + + + + + + +
+
+
{this.t('crd:supplier:address')}
+ {address &&
+ {address.get('streetAddress')}
+ {address.get('locality')} / +   + {address.get('postalCode')} / +   + {address.get('countryName')} +
} +
+
+
+
{this.t('crd:supplier:contact')}
+ {contact &&
+ {contact.get('name')}
+ {contact.get('email')}
+ {contact.get('telephone')} +
} +
+
+
+ ); + } +} + +class Supplier extends CRDPage { + constructor(...args) { + super(...args); + this.state = this.state || {}; + + this.injectSupplierFilter = cacheFn((filters, supplierId) => { + return filters.update('supplierId', Set(), supplierIds => supplierIds.add(supplierId)); + }); + + this.injectBidderFilter = cacheFn((filters, supplierId) => { + return filters.update('bidderId', Set(), supplierIds => supplierIds.add(supplierId)); + }); + + this.groupIndicators = cacheFn((indicatorTypesMapping) => { + const result = {}; + CORRUPTION_TYPES.forEach((corruptionType) => { result[corruptionType] = []; }); + if (indicatorTypesMapping) { + Object.keys(indicatorTypesMapping).forEach((indicatorName) => { + const indicator = indicatorTypesMapping[indicatorName]; + indicator.types.forEach(type => result[type].push(indicatorName)); + }); + } + return result; + }); + } + + componentDidMount(...args) { + super.componentDidMount(...args); + supplierId.assign('Supplier single', this.props.id); + } + + componentWillReceiveProps(nextProps) { + const { id } = this.props; + if (id != nextProps.id) { + supplierId.assign('supplierId', id); + } + } + + maybeGetFlagAnalysis() { + const { indicatorTypesMapping, id, data, filters, translations, requestNewData } = this.props; + + const nrFlagsByCorruptionType = {}; + CORRUPTION_TYPES.forEach((corruptionType) => { nrFlagsByCorruptionType[corruptionType] = 0; }); + data.get('nr-flags', List()).forEach((corruptionType) => { + nrFlagsByCorruptionType[corruptionType.get('type')] = corruptionType.get('indicatorCount'); + }); + + const indicators = this.groupIndicators(indicatorTypesMapping); + const noIndicators = Object + .keys(nrFlagsByCorruptionType) + .every(key => nrFlagsByCorruptionType[key] === 0); + + if (noIndicators) { + return ( +
+

This supplier has no flags

+
+ ); + } + + return ( +
+
+ {CORRUPTION_TYPES + .filter(corruptionType => nrFlagsByCorruptionType[corruptionType]) + .map((corruptionType) => { + return ( +
+

+ {this.t(`crd:corruptionType:${corruptionType}:pageTitle`)} +

+ + { + const toRemove = newData.filter(row => row.every(cell => cell.get('count') === 0)).keySeq(); + requestNewData(path.concat(['crosstab', corruptionType]), + newData.withMutations(data => { + toRemove.forEach(indicator => { + data.delete(indicator); + data.keySeq().forEach(key => { + data.deleteIn([key, indicator]); + }) + }) + }) + ); + }} + indicators={indicators[corruptionType]} + showRawNumbers + /> +
+ ); + })} +

{this.t('crd:supplier:table:procurementsWon')}

+ +
+ ); + } + + render() { + const { translations, doSearch, id, data } = this.props; + const totalFlags = data.getIn(['info', 'totalFlags']); + + return ( +
+ + + + + + {totalFlags === 0 &&
+

{this.t('crd:contracts:flagAnalysis')}

+

This supplier has no flags

+
} + + {!!totalFlags && this.maybeGetSections()} +
+ ); + } + + maybeGetSections() { + const { width, id, filters, styling, indicatorTypesMapping, translations } = this.props; + const donutSize = width / 3 - window.innerWidth / 20; + const barChartWidth = width / 2 - 100; + return ( +
+
+

{this.t('crd:supplier:generalStatistics')}

+
+ +
+
+ +
+
+ +
+
+
+

+ {this.t('crd:contracts:flagAnalysis')} +

+
+ + + + + +
+
+ + + + + +
+
+ {this.maybeGetFlagAnalysis()} +
+ ); + } +} + +export default Supplier; diff --git a/ui/oce/corruption-risk/suppliers/single/state.jsx b/ui/oce/corruption-risk/suppliers/single/state.jsx new file mode 100644 index 000000000..5138994f8 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/state.jsx @@ -0,0 +1,56 @@ +import { Set } from 'immutable'; +import { CRD, datefulFilters, indicatorIdsFlat, indicatorTypesMapping, API_ROOT } from '../../../state/oce-state'; +import { FlaggedNrMapping } from '../../archive/state'; + +export const SupplierState = CRD.substate({ + name: 'SupplierState' +}); + +export const supplierId = SupplierState.input({ + name: 'supplierId' +}); + +export const supplierFilters = SupplierState.mapping({ + name: 'supplierFilters', + deps: [datefulFilters, supplierId], + mapper: (filters, supplierId) => + filters.update('supplierId', Set(), supplierIds => supplierIds.add(supplierId)) +}); + +export const supplierFlaggedNrData = new FlaggedNrMapping({ + name: 'supplierFlaggedNrData', + filters: supplierFilters, + parent: SupplierState, +}); + +const winsAndFlagsURL = SupplierState.input({ + name: 'winsAndFlagsURL', + initial: `${API_ROOT}/supplierWinsPerProcuringEntity` +}) + +const winsAndFlagsRaw = SupplierState.remote({ + name: 'winsAndFlagsRaw', + url: winsAndFlagsURL, + params: supplierFilters +}); + +export const winsAndFlagsData = SupplierState.mapping({ + name: 'winsAndFlagsData', + deps: [winsAndFlagsRaw], + mapper(raw) { + return raw.map(({ count, countFlags, procuringEntityName}) => ({ + name: procuringEntityName, + wins: count, + flags: countFlags + })); + } +}); + +export const maxCommonDataLength = SupplierState.mapping({ + name: 'maxCommonDataLength', + deps: [winsAndFlagsData, supplierFlaggedNrData], + mapper: (a, b) => Math.min( + 5, + Math.max(a.length, b.length) + ), +}) diff --git a/ui/oce/corruption-risk/suppliers/single/style.less b/ui/oce/corruption-risk/suppliers/single/style.less new file mode 100644 index 000000000..c24910b04 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/style.less @@ -0,0 +1,59 @@ +.center-text.two-rows .secondary { + font-size: .3em; + margin-top: -1em; +} + +.total-flags-center-text span:not(:last-child)::after{ + display: inline-block; + content: '/'; + color: #144361; +} + +.center-text .multiplier { + font-size: 65%; + font-weight: bold; +} + +.supplier-page { + .flag-analysis { + clear: both; + + h2 { + padding-top: 50px; + } + } + + section.supplier-general-statistics { + h2 { + margin-bottom: 40px; + @media screen and (max-width: 1500px) { + margin-bottom: 20px; + } + + @media screen and (max-width: 1400px) { + margin-bottom: 10px; + } + + @media screen and (max-width: 1300px) { + margin-bottom: 0; + } + + } + } +} + +.recharts-tooltip-wrapper { + visibility: visible!important; + transform: none!important; +} + +.recharts-text.recharts-label { + tspan { + display: none; + } + + tspan:first-child { + display: block; + } +} + diff --git a/ui/oce/corruption-risk/suppliers/single/table/index.jsx b/ui/oce/corruption-risk/suppliers/single/table/index.jsx new file mode 100644 index 000000000..e2a633ed3 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/table/index.jsx @@ -0,0 +1,124 @@ +import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; +import translatable from '../../../../translatable'; +import { supplierProcurementsData, page, pageSize, supplierProcurementsCount } from './state'; +import BootstrapTableWrapper from '../../../archive/bootstrap-table-wrapper'; +import style from './style.less'; + +const NAME = 'supplierProcurementsComponent'; + +export default class Table extends translatable(React.PureComponent) { + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.data = []; + } + + componentDidMount() { + supplierProcurementsData.addListener(NAME, () => this.updateBindings()); + page.addListener(NAME, () => this.updateBindings()); + pageSize.addListener(NAME, () => this.updateBindings()); + supplierProcurementsCount.addListener(NAME, () => this.updateBindings()); + } + + componentWillUnmount() { + supplierProcurementsData.removeListener(NAME); + page.removeListener(NAME); + pageSize.removeListener(NAME); + supplierProcurementsCount.removeListener(NAME); + } + + updateBindings() { + Promise.all([ + supplierProcurementsData.getState(NAME), + page.getState(NAME), + pageSize.getState(NAME), + supplierProcurementsCount.getState(NAME), + ]).then(([data, page, pageSize, count]) => { + this.setState({ + data, + page, + pageSize, + count, + }) + }) + } + + formatTypes(data) { + return ( +
+ {data.map(datum => { + const translated = this.t(`crd:corruptionType:${datum.type}:name`); + return
{translated}: {datum.count}
; + })} +
+ ); + } + + formatFlags(data) { + return ( +
+ {data.map(indicator => ( +
+ {this.t(`crd:indicators:${indicator}:name`)} +
+ ))} +
+ ); + } + + formatDate(date) { + return new Date(date).toLocaleDateString(); + } + + formatPE(PEName, { PEId }) { + return ( + + {PEName} + + ); + } + + render() { + const { data, count } = this.state; + return ( + page.assign(NAME, newPage)} + onSizePerPageList={newPageSize => pageSize.assign(NAME, newPageSize)} + count={count} + containerClass="supplier-procurements-table" + columns={[{ + title: this.t('crd:contracts:baseInfo:procuringEntityName'), + dataField: 'PEName', + className: 'pe-name', + columnClassName: 'pe-name', + dataFormat: this.formatPE.bind(this), + }, { + title: this.t('crd:contracts:list:awardAmount'), + dataField: 'awardAmount', + }, { + title: this.t('crd:contracts:baseInfo:awardDate'), + dataField: 'awardDate', + dataFormat: this.formatDate.bind(this), + }, { + title: this.t('crd:supplier:table:nrBidders'), + dataField: 'nrBidders', + width: '10%', + }, { + title: this.t('crd:procurementsTable:noOfFlags'), + dataField: 'types', + dataFormat: this.formatTypes.bind(this), + width: '25%', + }, { + title: this.t('crd:supplier:table:flagName'), + dataField: 'flags', + dataFormat: this.formatFlags.bind(this), + width: '25%', + }]} + /> + ) + } +} + diff --git a/ui/oce/corruption-risk/suppliers/single/table/state.jsx b/ui/oce/corruption-risk/suppliers/single/table/state.jsx new file mode 100644 index 000000000..513268f25 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/table/state.jsx @@ -0,0 +1,81 @@ +import { SupplierState, supplierFilters } from '../state'; +import { API_ROOT } from '../../../../state/oce-state'; + +export const SupplierTableState = SupplierState.substate({ + name: 'SupplierTableState', +}); + +const supplierProcurementsEP = SupplierTableState.input({ + name: 'supplierProcurementsEP', + initial: `${API_ROOT}/flaggedRelease/all`, +}); + +export const page = SupplierTableState.input({ + name: 'page', + initial: 1, +}); + +export const pageSize = SupplierTableState.input({ + name: 'pageSize', + initial: 20, +}); + +const filters = SupplierTableState.mapping({ + name: 'filters', + deps: [supplierFilters, page, pageSize], + mapper: (supplierFilters, page, pageSize) => + supplierFilters + .set('pageSize', pageSize) + .set('pageNumber', page - 1) +}); + +const supplierProcurementsRaw = SupplierTableState.remote({ + name: 'supplierProcurementsRaw', + url: supplierProcurementsEP, + params: filters, +}); + +const findActiveAward = awards => + awards.find( + award => award.status === 'active' + ); + +function getAwardAmount(awards) { + const award = findActiveAward(awards); + const { value } = award; + return `${value.amount} ${value.currency}`; +} + +function getAwardDate(awards) { + const award = findActiveAward(awards); + return award.date; +} + +export const supplierProcurementsData = SupplierTableState.mapping({ + name: 'supplierProcurementsData', + deps: [supplierProcurementsRaw], + mapper: supplierProcurementsRaw => + supplierProcurementsRaw.map(datum => { + return { + id: datum.id, + PEName: datum.tender.procuringEntity.name, + PEId: datum.tender.procuringEntity.id, + awardAmount: getAwardAmount(datum.awards), + awardDate: getAwardDate(datum.awards), + nrBidders: datum.tender.numberOfTenderers, + types: datum.flags.flaggedStats, + flags: Object.keys(datum.flags).filter(key => datum.flags[key].value), + } + }), +}); + +const supplierProcurementsCountEP = SupplierTableState.input({ + name: 'supplierProcurementsCountEP', + initial: `${API_ROOT}/flaggedRelease/count`, +}); + +export const supplierProcurementsCount = SupplierTableState.remote({ + name: 'supplierProcurementsCount', + url: supplierProcurementsCountEP, + params: supplierFilters, +}); diff --git a/ui/oce/corruption-risk/suppliers/single/table/style.less b/ui/oce/corruption-risk/suppliers/single/table/style.less new file mode 100644 index 000000000..40abc7573 --- /dev/null +++ b/ui/oce/corruption-risk/suppliers/single/table/style.less @@ -0,0 +1,5 @@ +.supplier-procurements-table { + .pe-name { + width: 20%; + } +} diff --git a/ui/oce/corruption-risk/tagged-bar-chart.jsx b/ui/oce/corruption-risk/tagged-bar-chart.jsx new file mode 100644 index 000000000..bd275efb2 --- /dev/null +++ b/ui/oce/corruption-risk/tagged-bar-chart.jsx @@ -0,0 +1,123 @@ +import ReactDOM from 'react-dom'; +import { pluck } from '../tools'; +import PlotlyChart from './plotly-chart'; + +function mkGradient(id, colors) { + const stops = []; + const step = 100 / colors.length; + colors.forEach((color, index) => { + stops.push({ + color, + offset: step * index, + }); + stops.push({ + color, + offset: step * (index + 1), + }); + }); + + return ( + + {stops.map(({color, offset}) => + + )} + + ) +} + +class TaggedBarChart extends React.PureComponent { + fixYLabels() { + const { data } = this.props; + if (!data.length) return; + const deltaY = 0; + const $this = ReactDOM.findDOMNode(this); + const barHeight = $this.querySelector('.trace.bars .point').getBoundingClientRect().height; + + $this.querySelectorAll('.ytick').forEach(label => { + const { width } = label.getBoundingClientRect(); + label.setAttribute('transform', `translate(${width}, ${-barHeight - deltaY})`) + + if (navigator.userAgent.indexOf('Firefox') === -1) { + setTimeout(function() { + const { width } = label.getBoundingClientRect(); + label.setAttribute('transform', `translate(${width + 5}, ${-barHeight - deltaY})`) + }) + } + }); + } + + render() { + const { width, tags, data } = this.props; + const fstTag = Object.keys(tags)[0]; + const y = data.map(pluck('y')); + const dataSize = data.length; + const plotlyData = {}; + const gradients = {}; + let style = ''; + + Object.keys(tags).map(slug => { + const { color, name } = tags[slug]; + plotlyData[slug] = { + x: [0], + y, + name, + type: 'bar', + marker: { + color, + }, + hoverinfo: 'none', + orientation: 'h' + } + }); + + data.forEach((datum, index) => { + plotlyData[fstTag].x[index] = datum.x; + if (datum.tags.length > 1) { + const gradientSlug = datum.tags.join('_'); + gradients[gradientSlug] = gradients[gradientSlug] || + datum.tags.map(tag => tags[tag].color); + style += `#tagged-bar-chart .point:nth-child(${index + 1}) path {` + + `fill: url(#${gradientSlug})!important;}\n`; + } + }); + + return ( +
+ + + {Object.entries(gradients).map(([slug, colors]) => mkGradient(slug, colors))} + + +