`_)
* Add fields ``life.qbic.datamodel.dtos.business.Offer#itemsWithOverhead``, ``life.qbic.datamodel.dtos.business.Offer#itemsWithoutOverhead``,
-``life.qbic.datamodel.dtos.business.Offer#itemsWithOverheadNetPrice`` and ``life.qbic.datamodel.dtos.business.Offer#itemsWithoutOverheadNetPrice`` to Offer DTO (`#160 `_)
+ ``life.qbic.datamodel.dtos.business.Offer#itemsWithOverheadNetPrice`` and ``life.qbic.datamodel.dtos.business.Offer#itemsWithoutOverheadNetPrice`` to Offer DTO (`#160 `_)
**Fixed**
@@ -324,4 +348,3 @@ This project adheres to `Semantic Versioning `_.
**Dependencies**
**Deprecated**
-
diff --git a/docs/conf.py b/docs/conf.py
index bec81cf81b..70807a0db4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -55,9 +55,9 @@
# the built documents.
#
# The short X.Y version.
-version = '2.2.0'
+version = '2.3.0'
# The full version, including alpha/beta/rc tags.
-release = '2.2.0'
+release = '2.3.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/pom.xml b/pom.xml
index 2310284a31..bd3c867622 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
life.qbic
data-model-lib
- 2.2.0
+ 2.3.0
data-model-lib
http://github.com/qbicsoftware/data-model-lib
Data models. A collection of QBiC's central data models and DTOs.
diff --git a/qube.cfg b/qube.cfg
index 9a18fa84cc..929a2b7b02 100644
--- a/qube.cfg
+++ b/qube.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 2.2.0
+current_version = 2.3.0
[bumpversion_files_whitelisted]
dot_qube = .qube.yml
diff --git a/src/main/groovy/life/qbic/datamodel/dtos/business/Offer.groovy b/src/main/groovy/life/qbic/datamodel/dtos/business/Offer.groovy
index 59b0b81fad..dff3019983 100644
--- a/src/main/groovy/life/qbic/datamodel/dtos/business/Offer.groovy
+++ b/src/main/groovy/life/qbic/datamodel/dtos/business/Offer.groovy
@@ -91,6 +91,10 @@ class Offer {
* The net price of all items for which an overhead cost is not applicable, without overhead and taxes
*/
final double itemsWithoutOverheadNetPrice
+ /**
+ * The overhead ratio applied to the pricing dependent on the customer affiliation
+ */
+ final double overheadRatio
static class Builder {
@@ -122,7 +126,7 @@ class Offer {
double overheads
double itemsWithOverheadNet
double itemsWithoutOverheadNet
-
+ double overheadRatio
/**
* @deprecated Replaced with {@link #projectObjective}, since 2.1.0
*/
@@ -151,6 +155,7 @@ class Offer {
this.itemsWithOverheadNet = 0
this.itemsWithoutOverheadNet = 0
this.checksum = ""
+ this.overheadRatio = 0
/*
Deprecated
@@ -223,6 +228,11 @@ class Offer {
return this
}
+ Builder overheadRatio(double overheadRatio){
+ this.overheadRatio = overheadRatio
+ return this
+ }
+
Offer build() {
return new Offer(this)
}
@@ -258,6 +268,7 @@ class Offer {
this.itemsWithoutOverhead = builder.itemsWithoutOverhead
this.itemsWithOverheadNetPrice = builder.itemsWithOverheadNet
this.itemsWithoutOverheadNetPrice = builder.itemsWithoutOverheadNet
+ this.overheadRatio = builder.overheadRatio
/*
Deprecated properties
diff --git a/src/main/groovy/life/qbic/datamodel/dtos/business/ProductId.groovy b/src/main/groovy/life/qbic/datamodel/dtos/business/ProductId.groovy
index 0bf3c417f4..fbf17f95e3 100644
--- a/src/main/groovy/life/qbic/datamodel/dtos/business/ProductId.groovy
+++ b/src/main/groovy/life/qbic/datamodel/dtos/business/ProductId.groovy
@@ -23,27 +23,88 @@ class ProductId {
/**
* Identifying number used in conjunction with the type
*/
- private final String identifier
+ private final long uniqueId
+
+ /**
+ * A builder for ProductId instances.
+ */
+ static class Builder {
+ private String productType
+ private long uniqueId
+
+ /**
+ *
+ * @param productType
+ * @param identifier the unique id - will be interpreted as unsigned long
+ */
+ Builder(String productType, String identifier) {
+ this.productType = Objects.requireNonNull(productType)
+ this.uniqueId = Objects.requireNonNull(Long.parseUnsignedLong(identifier))
+ }
+
+ /**
+ *
+ * @param productType
+ * @param uniqueId the unique id - will be interpreted as unsigned long
+ */
+ Builder(String productType, long uniqueId) {
+ this.productType = Objects.requireNonNull(productType)
+ this.uniqueId = Objects.requireNonNull(uniqueId)
+ }
+
+ Builder productType(String productType) {
+ this.productType = productType
+ return this
+ }
+
+ Builder uniqueId(long identifier) {
+ this.uniqueId = identifier
+ return this
+ }
+ /**
+ * Constructs a product identifier based on the configuration of the builder
+ * @return
+ */
+ ProductId build() {
+ return new ProductId(this)
+ }
+ }
/**
* Creates an identifier object with the
*
* @param type describing the type of the underlying identifier
* @param identifier describes the identifying running number
+ * @deprecated please use {@link ProductId.Builder}
*/
-
+ @Deprecated
ProductId(String type, String identifier){
- this.type = Objects.requireNonNull(type, "type must not be null")
- this.identifier= Objects.requireNonNull(identifier, "version must not be null")
+ Builder builder = new Builder(type, identifier)
+ this.type = builder.productType
+ this.uniqueId = builder.uniqueId
+ }
+ private ProductId(Builder builder) {
+ this.type = builder.productType
+ this.uniqueId = builder.uniqueId
}
/**
* Returns the identifying running number
- * @return
+ * @return the identifier
+ * @deprecated please use {@link ProductId#getUniqueId()}
*/
+ @Deprecated
String getIdentifier() {
- return identifier
+ return uniqueId.toString()
+ }
+
+ /**
+ * Returns the identifying running number
+ * @return the identifier
+ */
+ Long getUniqueId() {
+ return uniqueId
}
/**
* Returns the type of the identifier
@@ -62,6 +123,6 @@ class ProductId {
*/
@Override
String toString() {
- return type + "_" + identifier
+ return "${type}_${uniqueId}"
}
}
\ No newline at end of file
diff --git a/src/main/groovy/life/qbic/datamodel/dtos/business/ProjectApplication.groovy b/src/main/groovy/life/qbic/datamodel/dtos/business/ProjectApplication.groovy
new file mode 100644
index 0000000000..1e833681c4
--- /dev/null
+++ b/src/main/groovy/life/qbic/datamodel/dtos/business/ProjectApplication.groovy
@@ -0,0 +1,81 @@
+package life.qbic.datamodel.dtos.business
+
+import life.qbic.datamodel.dtos.business.Customer
+import life.qbic.datamodel.dtos.business.OfferId
+import life.qbic.datamodel.dtos.business.ProjectManager
+import life.qbic.datamodel.dtos.projectmanagement.ProjectCode
+import life.qbic.datamodel.dtos.projectmanagement.ProjectSpace
+
+/**
+ * Information about a desired project registration in QBiC's project management system.
+ *
+ * Instances of this class describe a request to register a new project at QBiC.
+ *
+ * Beside the mandatory fields required to be passed to the constructor, there are two optional
+ * fields:
+ * 1: projectSpace
+ * 2: projectCode
+ * If any of these are not defined, then either of it will be created randomly.
+ *
+ * @since 2.3.0
+ */
+class ProjectApplication {
+
+ /**
+ * The associated offer
+ */
+ final OfferId linkedOffer
+
+ /**
+ * A short and precise project title
+ */
+ final String projectTitle
+
+ /**
+ * A descriptive project objective
+ */
+ final String projectObjective
+
+ /**
+ * A description about the experimental design
+ */
+ final String experimentalDesign
+
+ /**
+ * The associated project manager
+ */
+ final ProjectManager projectManager
+
+ /**
+ * The associated customer
+ */
+ final Customer customer
+
+ /**
+ * The requested project space the project shall be associated with
+ */
+ final ProjectSpace projectSpace
+
+ /**
+ * The desired project code
+ */
+ final ProjectCode projectCode
+
+ ProjectApplication(OfferId linkedOffer,
+ String projectTitle,
+ String projectObjective,
+ String experimentalDesign,
+ ProjectManager projectManager,
+ ProjectSpace projectSpace,
+ Customer customer,
+ ProjectCode code) {
+ this.linkedOffer = Objects.requireNonNull(linkedOffer)
+ this.projectTitle = Objects.requireNonNull(projectTitle)
+ this.projectObjective = Objects.requireNonNull(projectObjective)
+ this.experimentalDesign = Objects.requireNonNull(experimentalDesign)
+ this.projectManager = Objects.requireNonNull(projectManager)
+ this.customer = Objects.requireNonNull(customer)
+ this.projectSpace = Objects.requireNonNull(projectSpace)
+ this.projectCode = Objects.requireNonNull(code)
+ }
+}
diff --git a/src/main/groovy/life/qbic/datamodel/dtos/business/services/ProductUnit.groovy b/src/main/groovy/life/qbic/datamodel/dtos/business/services/ProductUnit.groovy
index e0265c0693..4c38252f7b 100644
--- a/src/main/groovy/life/qbic/datamodel/dtos/business/services/ProductUnit.groovy
+++ b/src/main/groovy/life/qbic/datamodel/dtos/business/services/ProductUnit.groovy
@@ -9,7 +9,8 @@ enum ProductUnit {
PER_GIGABYTE("Gigabyte"),
PER_SAMPLE("Sample"),
- PER_DATASET("Dataset")
+ PER_DATASET("Dataset"),
+ PER_HOUR("Hour")
/**
Holds the String value of the enum
diff --git a/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/Project.groovy b/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/Project.groovy
new file mode 100644
index 0000000000..38470b2d15
--- /dev/null
+++ b/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/Project.groovy
@@ -0,0 +1,66 @@
+package life.qbic.datamodel.dtos.projectmanagement
+
+import life.qbic.datamodel.dtos.business.OfferId
+
+/**
+ * A typical scientific QBiC project.
+ *
+ * Describes and provides information about a scientific project
+ * at QBiC.
+ *
+ * @since 2.3.0
+ */
+class Project {
+
+ /**
+ * A short but descriptive project title
+ */
+ final String projectTitle
+
+ /**
+ * QBiC's internal project identifier
+ */
+ final ProjectIdentifier projectId
+
+ /**
+ * The associated offer
+ */
+ final OfferId linkedOffer
+
+ private Project(Builder builder) {
+ this.projectId = Objects.requireNonNull(builder.projectIdentifier)
+ this.projectTitle = Objects.requireNonNull(builder.projectTitle)
+ this.linkedOffer = builder.linkedOfferId
+ }
+
+ static class Builder {
+ private ProjectIdentifier projectIdentifier
+ private String projectTitle
+ private OfferId linkedOfferId
+
+ Builder(ProjectIdentifier projectIdentifier, String projectTitle) {
+ this.projectIdentifier = projectIdentifier
+ this.projectTitle = projectTitle
+ this.linkedOfferId = null
+ }
+
+ Builder projectIdentifier(ProjectIdentifier projectIdentifier) {
+ this.projectIdentifier = projectIdentifier
+ return this
+ }
+
+ Builder projectTitle(String projectTitle) {
+ this.projectTitle = projectTitle
+ return this
+ }
+
+ Builder linkedOfferId(OfferId offerId) {
+ this.linkedOfferId = offerId
+ return this
+ }
+
+ Project build() {
+ return new Project(this)
+ }
+ }
+}
diff --git a/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectCode.groovy b/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectCode.groovy
new file mode 100644
index 0000000000..34d93efa8f
--- /dev/null
+++ b/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectCode.groovy
@@ -0,0 +1,51 @@
+package life.qbic.datamodel.dtos.projectmanagement
+
+/**
+ * Describes the project code, that identifies the project within a project space.
+ *
+ * A project code is the unique identifier of a QBiC project within a project space.
+ *
+ * A project code can be validated with the following regular expression: "Q[A-X0-9]{4}".
+ *
+ * @since 2.3.0
+ */
+class ProjectCode {
+
+ final String code
+
+ private static final def REGEX = ~'Q[A-X0-9]{4}'
+
+ /**
+ * Constructs a project code instance based on the given code string.
+ * @param code The project code
+ * @throws IllegalArgumentException If the code parameter does not match format requirements.
+ */
+ ProjectCode(String code) throws IllegalArgumentException {
+ Objects.requireNonNull(code, "Code must not be null")
+ this.code = code.trim()
+ validateCode()
+ }
+
+ private void validateCode() {
+ if(! REGEX.matcher(code).matches()) {
+ throw new IllegalArgumentException("${code} is not a valid project code.")
+ }
+ }
+
+ @Override
+ String toString() {
+ return this.code
+ }
+
+ @Override
+ boolean equals(Object obj) {
+ if (this.is(obj)) {
+ return true
+ }
+ if (!obj instanceof ProjectCode) {
+ return false
+ }
+ ProjectCode otherCode = (ProjectCode) obj
+ return this.code.equals(otherCode.code)
+ }
+}
diff --git a/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectIdentifier.groovy b/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectIdentifier.groovy
new file mode 100644
index 0000000000..25333d09e6
--- /dev/null
+++ b/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectIdentifier.groovy
@@ -0,0 +1,36 @@
+package life.qbic.datamodel.dtos.projectmanagement
+
+/**
+ * Global project identifier for QBiC projects
+ *
+ * Identifies a project by its project space and project code.
+ *
+ * @since 2.3.0
+ */
+class ProjectIdentifier {
+
+ /**
+ * The associated project space
+ */
+ final ProjectSpace projectSpace
+
+ /**
+ * The associated project code within the project space
+ */
+ final ProjectCode projectCode
+
+ /**
+ * Constructor for a project identifier.
+ * @param projectSpace The project space the project is associated with
+ * @param projectCode The project code, identifying the project within the project space
+ */
+ ProjectIdentifier(ProjectSpace projectSpace, ProjectCode projectCode) {
+ this.projectSpace = Objects.requireNonNull(projectSpace)
+ this.projectCode = Objects.requireNonNull(projectCode)
+ }
+
+ @Override
+ String toString() {
+ return "${projectSpace.toString()}/${projectCode.toString()}"
+ }
+}
diff --git a/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectSpace.groovy b/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectSpace.groovy
new file mode 100644
index 0000000000..f2383ba46b
--- /dev/null
+++ b/src/main/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectSpace.groovy
@@ -0,0 +1,62 @@
+package life.qbic.datamodel.dtos.projectmanagement
+
+/**
+ * Represents a QBiC project space
+ *
+ * A space is a logical grouping of projects that have the same context.
+ *
+ * A project space has a name that follows this convention:
+ *
+ *
+ * - No white space
+ * - Capital letters only
+ * - Inner white space is replaced by `_`
+ * - Trailing and leading white space are trimmed
+ * - Dashes are replaced by `_`
+ *
+ * @since 2.3.0
+ */
+final class ProjectSpace {
+
+ /**
+ * The project space name
+ */
+ final String name
+
+ ProjectSpace(String name) {
+ final def space = Objects.requireNonNull(name, "Space name must not be null.")
+ this.name = formatSpaceName(space)
+ }
+
+ private static String formatSpaceName(String name) {
+ def capitalizedName = name.trim().toUpperCase()
+ def refactoredName = capitalizedName.replaceAll("\\s+", "_")
+ .replaceAll("-", "_")
+ return refactoredName
+ }
+
+ /**
+ * Returns a string representation of the space name following the format conventions stated
+ * in the class description.
+ *
+ * Example: "my example space " -> "MY_EXAMPLE_SPACE"
+ *
+ * @return The project space name
+ */
+ @Override
+ String toString() {
+ return this.name
+ }
+
+ @Override
+ boolean equals(Object obj) {
+ if (this.is(obj)) {
+ return true
+ }
+ if (!obj instanceof ProjectSpace) {
+ return false
+ }
+ ProjectSpace otherSpace = (ProjectSpace) obj
+ return this.name.equals(otherSpace.name)
+ }
+}
diff --git a/src/test/groovy/life/qbic/datamodel/dtos/business/OfferSpec.groovy b/src/test/groovy/life/qbic/datamodel/dtos/business/OfferSpec.groovy
index d0b1d693d0..8c797d0be2 100644
--- a/src/test/groovy/life/qbic/datamodel/dtos/business/OfferSpec.groovy
+++ b/src/test/groovy/life/qbic/datamodel/dtos/business/OfferSpec.groovy
@@ -33,10 +33,11 @@ class OfferSpec extends Specification {
ProductItem item = new ProductItem(2,new Sequencing("DNA Sequencing","This is a sequencing package",1.50, ProductUnit.PER_SAMPLE, "1"))
double itemsWithOverheadNet = 123
double itemsWithoutOverheadNet = 456
+ double overheadRatio = 0.2
when:
Offer testOffer =
new Offer.Builder(customer, projectManager, "Archer", "Cartoon Series", selectedAffiliation)
- .modificationDate(date).expirationDate(date).totalPrice(price).identifier(offerId).taxes(vat).overheads(overhead).netPrice(net).items([item]).itemsWithOverhead([item]).itemsWithoutOverhead([item]).itemsWithOverheadNet(itemsWithOverheadNet).itemsWithoutOverheadNet(itemsWithoutOverheadNet)
+ .modificationDate(date).expirationDate(date).totalPrice(price).identifier(offerId).taxes(vat).overheads(overhead).netPrice(net).items([item]).itemsWithOverhead([item]).itemsWithoutOverhead([item]).itemsWithOverheadNet(itemsWithOverheadNet).itemsWithoutOverheadNet(itemsWithoutOverheadNet).overheadRatio(overheadRatio)
.build()
then:
@@ -57,6 +58,7 @@ class OfferSpec extends Specification {
testOffer.getItemsWithoutOverhead() == [item]
testOffer.getItemsWithOverheadNetPrice() == 123
testOffer.getItemsWithoutOverheadNetPrice() == 456
+ testOffer.overheadRatio == 0.2
}
def "Missing optional Field definitions shall haven null values in an Offer object"() {
diff --git a/src/test/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectCodeSpec.groovy b/src/test/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectCodeSpec.groovy
new file mode 100644
index 0000000000..764af16413
--- /dev/null
+++ b/src/test/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectCodeSpec.groovy
@@ -0,0 +1,35 @@
+package life.qbic.datamodel.dtos.projectmanagement
+
+import spock.lang.Specification
+
+/**
+ * Tests the project code vaidation
+ *
+ * @since 2.3.0
+ */
+class ProjectCodeSpec extends Specification {
+
+ def "A String violating the project code format standard shall throw an IllegalArgumentException" () {
+ given:
+ String invalidCode = " M-ABCD "
+
+ when:
+ new ProjectCode(invalidCode)
+
+ then:
+ thrown(IllegalArgumentException)
+
+ }
+
+ def "A String sticking to the project code format standard shall create a ProjectCode object instance successfully"() {
+ given:
+ String validCode = " QABCD "
+
+ when:
+ ProjectCode projectCode = new ProjectCode(validCode)
+
+ then:
+ noExceptionThrown()
+ assert projectCode.code.equals("QABCD")
+ }
+}
diff --git a/src/test/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectSpaceSpec.groovy b/src/test/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectSpaceSpec.groovy
new file mode 100644
index 0000000000..a10f8a88b7
--- /dev/null
+++ b/src/test/groovy/life/qbic/datamodel/dtos/projectmanagement/ProjectSpaceSpec.groovy
@@ -0,0 +1,33 @@
+package life.qbic.datamodel.dtos.projectmanagement
+
+import spock.lang.Specification
+
+/**
+ * Tests the formatting of the project space
+ *
+ * @since 2.3.0
+ */
+class ProjectSpaceSpec extends Specification {
+
+ def "A string including inner white space will be replaced with underscore chars"() {
+ given:
+ String projectSpaceName = " my new space "
+
+ when:
+ ProjectSpace space = new ProjectSpace(projectSpaceName)
+
+ then:
+ space.name.equals("MY_NEW_SPACE")
+ }
+
+ def "Dashes are replaced by underscores"() {
+ given:
+ String projectSpaceName = "my-new-space"
+
+ when:
+ ProjectSpace space = new ProjectSpace(projectSpaceName)
+
+ then:
+ space.name.equals("MY_NEW_SPACE")
+ }
+}