Implemented saving, changing, retrieving, and deleting balance segment sets.
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSet.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSet.java
index 7d4c3da..a2daf1c 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSet.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSet.java
@@ -22,7 +22,6 @@
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
-import java.util.SortedSet;
/**
* @author Myrle Krantz
@@ -32,7 +31,7 @@
@ValidIdentifier
private String identifier;
- private SortedSet<BigDecimal> segments;
+ private List<BigDecimal> segments;
@ValidIdentifiers
private List<String> segmentIdentifiers;
@@ -40,12 +39,6 @@
public BalanceSegmentSet() {
}
- public BalanceSegmentSet(String identifier, SortedSet<BigDecimal> segments, List<String> segmentIdentifiers) {
- this.identifier = identifier;
- this.segments = segments;
- this.segmentIdentifiers = segmentIdentifiers;
- }
-
public String getIdentifier() {
return identifier;
}
@@ -54,11 +47,11 @@
this.identifier = identifier;
}
- public SortedSet<BigDecimal> getSegments() {
+ public List<BigDecimal> getSegments() {
return segments;
}
- public void setSegments(SortedSet<BigDecimal> segments) {
+ public void setSegments(List<BigDecimal> segments) {
this.segments = segments;
}
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidSegmentList.java b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidSegmentList.java
index aeb02ec..96fc98d 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidSegmentList.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidSegmentList.java
@@ -37,11 +37,20 @@
if (value.getSegmentIdentifiers() == null)
return false;
- if (value.getSegments().size() + 1 != value.getSegmentIdentifiers().size())
+ if (value.getSegments().size() == 0)
return false;
- for (final BigDecimal segment : value.getSegments()) {
- if (segment.compareTo(BigDecimal.ZERO) <= 0)
+ if (value.getSegments().size() != value.getSegmentIdentifiers().size())
+ return false;
+
+ if (value.getSegments().get(0).compareTo(BigDecimal.ZERO) != 0)
+ return false;
+
+ for (int i = 0; i < value.getSegments().size() -1; i++) {
+ final BigDecimal segment1 = value.getSegments().get(i);
+ final BigDecimal segment2 = value.getSegments().get(i+1);
+
+ if (segment1.compareTo(segment2) > 0)
return false;
}
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSetTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSetTest.java
index c125654..d787a96 100644
--- a/api/src/test/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSetTest.java
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSetTest.java
@@ -34,7 +34,7 @@
protected BalanceSegmentSet createValidTestSubject() {
final BalanceSegmentSet ret = new BalanceSegmentSet();
ret.setIdentifier("valid");
- ret.setSegments(new TreeSet<>(Arrays.asList(BigDecimal.valueOf(100), BigDecimal.valueOf(10_000))));
+ ret.setSegments(Arrays.asList(BigDecimal.ZERO, BigDecimal.valueOf(100), BigDecimal.valueOf(10_000)));
ret.setSegmentIdentifiers(Arrays.asList("small", "medium", "large"));
return ret;
}
@@ -54,8 +54,14 @@
ret.add(new ValidationTestCase<BalanceSegmentSet>("too short identifier list")
.adjustment(x -> x.setSegmentIdentifiers(Arrays.asList("small", "large")))
.valid(false));
- ret.add(new ValidationTestCase<BalanceSegmentSet>("negative segmentation")
- .adjustment(x -> x.setSegments(new TreeSet<>(Arrays.asList(BigDecimal.ONE.negate(), BigDecimal.valueOf(100)))))
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("too short segment list")
+ .adjustment(x -> x.setSegments(Arrays.asList(BigDecimal.ZERO, BigDecimal.valueOf(100))))
+ .valid(false));
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("non-zero first entry")
+ .adjustment(x -> x.setSegments(Arrays.asList(BigDecimal.ONE, BigDecimal.valueOf(100), BigDecimal.valueOf(10_000))))
+ .valid(false));
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("mis-ordered segmentation")
+ .adjustment(x -> x.setSegments(Arrays.asList(BigDecimal.ZERO, BigDecimal.valueOf(10_000), BigDecimal.valueOf(100))))
.valid(false));
ret.add(new ValidationTestCase<BalanceSegmentSet>("invalid identifier")
.adjustment(x -> x.setIdentifier("//"))
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestBalanceSegmentSets.java b/component-test/src/main/java/io/mifos/portfolio/TestBalanceSegmentSets.java
index 001be34..e4852e6 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestBalanceSegmentSets.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestBalanceSegmentSets.java
@@ -25,7 +25,6 @@
import java.math.BigDecimal;
import java.util.Arrays;
-import java.util.TreeSet;
/**
* @author Myrle Krantz
@@ -37,8 +36,11 @@
final BalanceSegmentSet balanceSegmentSet = new BalanceSegmentSet();
balanceSegmentSet.setIdentifier(testEnvironment.generateUniqueIdentifer("bss"));
- balanceSegmentSet.setSegments(new TreeSet<>(Arrays.asList(BigDecimal.TEN, BigDecimal.valueOf(10_000))));
- balanceSegmentSet.setSegmentIdentifiers(Arrays.asList("a", "b", "c"));
+ balanceSegmentSet.setSegments(Arrays.asList(
+ BigDecimal.ZERO.setScale(4, BigDecimal.ROUND_HALF_EVEN),
+ BigDecimal.TEN.setScale(4, BigDecimal.ROUND_HALF_EVEN),
+ BigDecimal.valueOf(10_000_0000, 4)));
+ balanceSegmentSet.setSegmentIdentifiers(Arrays.asList("abc", "def", "ghi"));
portfolioManager.createBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BALANCE_SEGMENT_SET, new BalanceSegmentSetEvent(product.getIdentifier(), balanceSegmentSet.getIdentifier())));
@@ -46,8 +48,11 @@
final BalanceSegmentSet createdBalanceSegmentSet = portfolioManager.getBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet.getIdentifier());
Assert.assertEquals(balanceSegmentSet, createdBalanceSegmentSet);
- balanceSegmentSet.setSegments(new TreeSet<>(Arrays.asList(BigDecimal.valueOf(100), BigDecimal.valueOf(10_000))));
- balanceSegmentSet.setSegmentIdentifiers(Arrays.asList("a", "b", "c"));
+ balanceSegmentSet.setSegments(Arrays.asList(
+ BigDecimal.ZERO.setScale(4, BigDecimal.ROUND_HALF_EVEN),
+ BigDecimal.valueOf(100_0000, 4),
+ BigDecimal.valueOf(10_000_0000, 4)));
+ balanceSegmentSet.setSegmentIdentifiers(Arrays.asList("abc", "def", "ghi"));
portfolioManager.changeBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet.getIdentifier(), balanceSegmentSet);
Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_BALANCE_SEGMENT_SET, new BalanceSegmentSetEvent(product.getIdentifier(), balanceSegmentSet.getIdentifier())));
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeBalanceSegmentSetCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeBalanceSegmentSetCommand.java
new file mode 100644
index 0000000..19edb4c
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeBalanceSegmentSetCommand.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.command;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChangeBalanceSegmentSetCommand {
+ private final String productIdentifier;
+ private final BalanceSegmentSet instance;
+
+ public ChangeBalanceSegmentSetCommand(String productIdentifier, BalanceSegmentSet instance) {
+ this.productIdentifier = productIdentifier;
+ this.instance = instance;
+ }
+
+ public String getProductIdentifier() {
+ return productIdentifier;
+ }
+
+ public BalanceSegmentSet getInstance() {
+ return instance;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ChangeBalanceSegmentSetCommand that = (ChangeBalanceSegmentSetCommand) o;
+ return Objects.equals(productIdentifier, that.productIdentifier) &&
+ Objects.equals(instance, that.instance);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(productIdentifier, instance);
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeBalanceSegmentSetCommand{" +
+ "productIdentifier='" + productIdentifier + '\'' +
+ ", instance=" + instance +
+ '}';
+ }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/CreateBalanceSegmentSetCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/CreateBalanceSegmentSetCommand.java
new file mode 100644
index 0000000..a5a4925
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/CreateBalanceSegmentSetCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.command;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CreateBalanceSegmentSetCommand {
+ private final String productIdentifier;
+ private final BalanceSegmentSet instance;
+
+ public CreateBalanceSegmentSetCommand(String productIdentifier, BalanceSegmentSet instance) {
+ this.productIdentifier = productIdentifier;
+ this.instance = instance;
+ }
+
+ public String getProductIdentifier() {
+ return productIdentifier;
+ }
+
+ public BalanceSegmentSet getInstance() {
+ return instance;
+ }
+
+ @Override
+ public String toString() {
+ return "CreateBalanceSegmentSetCommand{" +
+ "productIdentifier='" + productIdentifier + '\'' +
+ ", instance=" + instance +
+ '}';
+ }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteBalanceSegmentSetCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteBalanceSegmentSetCommand.java
new file mode 100644
index 0000000..cb4ac07
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteBalanceSegmentSetCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.command;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DeleteBalanceSegmentSetCommand {
+ private final String productIdentifier;
+ private final String balanceSegmentSetIdentifier;
+
+ public DeleteBalanceSegmentSetCommand(String productIdentifier, String balanceSegmentSetIdentifier) {
+ this.productIdentifier = productIdentifier;
+ this.balanceSegmentSetIdentifier = balanceSegmentSetIdentifier;
+ }
+
+ public String getProductIdentifier() {
+ return productIdentifier;
+ }
+
+ public String getBalanceSegmentSetIdentifier() {
+ return balanceSegmentSetIdentifier;
+ }
+
+ @Override
+ public String toString() {
+ return "DeleteBalanceSegmentSetCommand{" +
+ "productIdentifier='" + productIdentifier + '\'' +
+ ", balanceSegmentSetIdentifier='" + balanceSegmentSetIdentifier + '\'' +
+ '}';
+ }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/BalanceSegmentSetCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/BalanceSegmentSetCommandHandler.java
new file mode 100644
index 0000000..dd8a7a3
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/BalanceSegmentSetCommandHandler.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.command.handler;
+
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.CommandLogLevel;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.portfolio.api.v1.events.BalanceSegmentSetEvent;
+import io.mifos.portfolio.api.v1.events.EventConstants;
+import io.mifos.portfolio.service.internal.command.ChangeBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.command.CreateBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.command.DeleteBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.mapper.BalanceSegmentSetMapper;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentSetEntity;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentSetRepository;
+import io.mifos.portfolio.service.internal.repository.ProductEntity;
+import io.mifos.portfolio.service.internal.repository.ProductRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Aggregate
+public class BalanceSegmentSetCommandHandler {
+ private final BalanceSegmentSetRepository balanceSegmentSetRepository;
+ private final ProductRepository productRepository;
+
+ @Autowired
+ public BalanceSegmentSetCommandHandler(
+ final BalanceSegmentSetRepository balanceSegmentSetRepository,
+ final ProductRepository productRepository) {
+ this.balanceSegmentSetRepository = balanceSegmentSetRepository;
+ this.productRepository = productRepository;
+ }
+
+ @Transactional
+ @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.POST_BALANCE_SEGMENT_SET)
+ public BalanceSegmentSetEvent process(final CreateBalanceSegmentSetCommand createBalanceSegmentSetCommand) {
+ final ProductEntity product = productRepository.findByIdentifier(createBalanceSegmentSetCommand.getProductIdentifier())
+ .orElseThrow(() -> ServiceException.notFound("Product with identifier ''{0}'' doesn''t exist.", createBalanceSegmentSetCommand.getProductIdentifier()));
+
+ final List<BalanceSegmentSetEntity> balanceSegmentSetEntities = BalanceSegmentSetMapper.map(
+ createBalanceSegmentSetCommand.getInstance(), product);
+
+ balanceSegmentSetRepository.save(balanceSegmentSetEntities);
+
+ return new BalanceSegmentSetEvent(
+ createBalanceSegmentSetCommand.getProductIdentifier(),
+ createBalanceSegmentSetCommand.getInstance().getIdentifier());
+ }
+
+ @Transactional
+ @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.PUT_BALANCE_SEGMENT_SET)
+ public BalanceSegmentSetEvent process(final ChangeBalanceSegmentSetCommand changeBalanceSegmentSetCommand) {
+ final ProductEntity product = productRepository.findByIdentifier(changeBalanceSegmentSetCommand.getProductIdentifier())
+ .orElseThrow(() -> ServiceException.notFound("Product with identifier ''{0}'' doesn''t exist.", changeBalanceSegmentSetCommand.getProductIdentifier()));
+
+ final List<BalanceSegmentSetEntity> balanceSegmentSets = balanceSegmentSetRepository.findByProductIdentifierAndSegmentSetIdentifier(
+ changeBalanceSegmentSetCommand.getProductIdentifier(),
+ changeBalanceSegmentSetCommand.getInstance().getIdentifier())
+ .collect(Collectors.toList());
+ if (balanceSegmentSets.isEmpty())
+ throw ServiceException.notFound("Segment set with identifier ''{0}.{1}'' doesn''t exist.",
+ changeBalanceSegmentSetCommand.getProductIdentifier(),
+ changeBalanceSegmentSetCommand.getInstance().getIdentifier());
+
+ balanceSegmentSetRepository.deleteInBatch(balanceSegmentSets);
+
+ final List<BalanceSegmentSetEntity> balanceSegmentSetEntities = BalanceSegmentSetMapper.map(
+ changeBalanceSegmentSetCommand.getInstance(), product);
+
+ balanceSegmentSetRepository.save(balanceSegmentSetEntities);
+
+ return new BalanceSegmentSetEvent(
+ changeBalanceSegmentSetCommand.getProductIdentifier(),
+ changeBalanceSegmentSetCommand.getInstance().getIdentifier());
+ }
+
+ @Transactional
+ @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.DELETE_BALANCE_SEGMENT_SET)
+ public BalanceSegmentSetEvent process(final DeleteBalanceSegmentSetCommand deleteBalanceSegmentSetCommand) {
+ final List<BalanceSegmentSetEntity> balanceSegmentSets = balanceSegmentSetRepository.findByProductIdentifierAndSegmentSetIdentifier(
+ deleteBalanceSegmentSetCommand.getProductIdentifier(),
+ deleteBalanceSegmentSetCommand.getBalanceSegmentSetIdentifier())
+ .collect(Collectors.toList());
+ if (balanceSegmentSets.isEmpty())
+ throw ServiceException.notFound("Segment set with identifier ''{0}.{1}'' doesn''t exist.",
+ deleteBalanceSegmentSetCommand.getProductIdentifier(),
+ deleteBalanceSegmentSetCommand.getBalanceSegmentSetIdentifier());
+
+ balanceSegmentSetRepository.deleteInBatch(balanceSegmentSets);
+
+ return new BalanceSegmentSetEvent(
+ deleteBalanceSegmentSetCommand.getProductIdentifier(),
+ deleteBalanceSegmentSetCommand.getBalanceSegmentSetIdentifier());
+ }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/BalanceSegmentSetMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/BalanceSegmentSetMapper.java
new file mode 100644
index 0000000..c42dfd0
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/BalanceSegmentSetMapper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.mapper;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentSetEntity;
+import io.mifos.portfolio.service.internal.repository.ProductEntity;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+public class BalanceSegmentSetMapper {
+ public static List<BalanceSegmentSetEntity> map(final BalanceSegmentSet instance, final ProductEntity product) {
+ return
+ Stream.iterate(0, i -> i+1).limit(instance.getSegmentIdentifiers().size())
+ .map(i -> {
+ final BalanceSegmentSetEntity ret = new BalanceSegmentSetEntity();
+ ret.setProduct(product);
+ ret.setSegmentSetIdentifier(instance.getIdentifier());
+ ret.setSegmentIdentifier(instance.getSegmentIdentifiers().get(i));
+ ret.setLowerBound(instance.getSegments().get(i));
+ return ret;
+ })
+ .collect(Collectors.toList());
+ }
+
+ public static Optional<BalanceSegmentSet> map(final Stream<BalanceSegmentSetEntity> instances) {
+ final BalanceSegmentSet ret = new BalanceSegmentSet();
+ ret.setSegments(new ArrayList<>());
+ ret.setSegmentIdentifiers(new ArrayList<>());
+ instances.sorted(Comparator.comparing(BalanceSegmentSetEntity::getLowerBound))
+ .forEach(seg -> {
+ ret.setIdentifier(seg.getSegmentSetIdentifier());
+ ret.getSegments().add(seg.getLowerBound());
+ ret.getSegmentIdentifiers().add(seg.getSegmentIdentifier());
+ });
+ if (ret.getSegments().isEmpty())
+ return Optional.empty();
+ else
+ return Optional.of(ret);
+ }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentSetEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentSetEntity.java
new file mode 100644
index 0000000..ee8c5c2
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentSetEntity.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.repository;
+
+import javax.persistence.*;
+import java.math.BigDecimal;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@Entity
+@Table(name = "bastet_p_balance_segs")
+public class BalanceSegmentSetEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "product_id", nullable = false)
+ private ProductEntity product;
+
+ @Column(name = "seg_set_identifier", nullable = false)
+ private String segmentSetIdentifier;
+
+ @Column(name = "segment_identifier", nullable = false)
+ private String segmentIdentifier;
+
+ @Column(name = "lower_bound")
+ private BigDecimal lowerBound;
+
+ public BalanceSegmentSetEntity() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public ProductEntity getProduct() {
+ return product;
+ }
+
+ public void setProduct(ProductEntity product) {
+ this.product = product;
+ }
+
+ public String getSegmentSetIdentifier() {
+ return segmentSetIdentifier;
+ }
+
+ public void setSegmentSetIdentifier(String segmentSetIdentifier) {
+ this.segmentSetIdentifier = segmentSetIdentifier;
+ }
+
+ public String getSegmentIdentifier() {
+ return segmentIdentifier;
+ }
+
+ public void setSegmentIdentifier(String segmentIdentifier) {
+ this.segmentIdentifier = segmentIdentifier;
+ }
+
+ public BigDecimal getLowerBound() {
+ return lowerBound;
+ }
+
+ public void setLowerBound(BigDecimal lowerBound) {
+ this.lowerBound = lowerBound;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BalanceSegmentSetEntity that = (BalanceSegmentSetEntity) o;
+ return Objects.equals(product, that.product) &&
+ Objects.equals(segmentSetIdentifier, that.segmentSetIdentifier) &&
+ Objects.equals(segmentIdentifier, that.segmentIdentifier);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(product, segmentSetIdentifier, segmentIdentifier);
+ }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentSetRepository.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentSetRepository.java
new file mode 100644
index 0000000..9aac27d
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentSetRepository.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Repository
+public interface BalanceSegmentSetRepository extends JpaRepository<BalanceSegmentSetEntity, Long> {
+ Stream<BalanceSegmentSetEntity> findByProductIdentifierAndSegmentSetIdentifier(String productIdentifier, String segmentSetIdentifier);
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/BalanceSegmentSetService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/BalanceSegmentSetService.java
new file mode 100644
index 0000000..451a57e
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/BalanceSegmentSetService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.service;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+import io.mifos.portfolio.service.internal.mapper.BalanceSegmentSetMapper;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentSetRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class BalanceSegmentSetService {
+ private final BalanceSegmentSetRepository balanceSegmentSetRepository;
+
+ @Autowired
+ public BalanceSegmentSetService(
+ final BalanceSegmentSetRepository balanceSegmentSetRepository) {
+ this.balanceSegmentSetRepository = balanceSegmentSetRepository;
+ }
+
+ public boolean existsByIdentifier(final String productIdentifier, final String balanceSegmentSetIdentifier)
+ {
+ //TODO: replace with existsBy once we've upgraded to spring data 1.11 or later.
+ return balanceSegmentSetRepository
+ .findByProductIdentifierAndSegmentSetIdentifier(productIdentifier, balanceSegmentSetIdentifier)
+ .findAny().isPresent();
+ }
+
+ public Optional<BalanceSegmentSet> findByIdentifier(
+ final String productIdentifier,
+ final String balanceSegmentSetIdentifier) {
+ return BalanceSegmentSetMapper.map(balanceSegmentSetRepository
+ .findByProductIdentifierAndSegmentSetIdentifier(productIdentifier, balanceSegmentSetIdentifier));
+ }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/BalanceSegmentSetRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/BalanceSegmentSetRestController.java
new file mode 100644
index 0000000..a5ca9cc
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/BalanceSegmentSetRestController.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.portfolio.api.v1.PermittableGroupIds;
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+import io.mifos.portfolio.service.internal.command.ChangeBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.command.CreateBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.command.DeleteBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.service.BalanceSegmentSetService;
+import io.mifos.portfolio.service.internal.service.CaseService;
+import io.mifos.portfolio.service.internal.service.ProductService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+/**
+ * @author Myrle Krantz
+ */
+@RestController
+@RequestMapping("/products/{productidentifier}/balancesegmentsets/")
+public class BalanceSegmentSetRestController {
+
+ private final CommandGateway commandGateway;
+ private final ProductService productService;
+ private final BalanceSegmentSetService balanceSegmentSetService;
+ private final CaseService caseService;
+
+ public BalanceSegmentSetRestController(final CommandGateway commandGateway,
+ final ProductService productService,
+ final BalanceSegmentSetService balanceSegmentSetService,
+ final CaseService caseService) {
+ this.commandGateway = commandGateway;
+ this.productService = productService;
+ this.balanceSegmentSetService = balanceSegmentSetService;
+ this.caseService = caseService;
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
+ @RequestMapping(
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public @ResponseBody
+ ResponseEntity<Void> createBalanceSegmentSet(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @RequestBody @Valid final BalanceSegmentSet instance) {
+ checkThatProductExists(productIdentifier);
+ checkThatSegmentSetDoesntExist(productIdentifier, instance.getIdentifier());
+ checkProductChangeable(productIdentifier);
+
+ this.commandGateway.process(new CreateBalanceSegmentSetCommand(productIdentifier, instance));
+ return new ResponseEntity<>(HttpStatus.ACCEPTED);
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
+ @RequestMapping(
+ value = "{balancesegmentsetidentifier}",
+ method = RequestMethod.GET,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ public @ResponseBody BalanceSegmentSet getBalanceSegmentSet(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier) {
+ return balanceSegmentSetService.findByIdentifier(productIdentifier, balanceSegmentSetIdentifier)
+ .orElseThrow(() -> ServiceException.notFound(
+ "Segment set with identifier ''{0}.{1}'' doesn''t exist.", productIdentifier, balanceSegmentSetIdentifier));
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
+ @RequestMapping(
+ value = "{balancesegmentsetidentifier}",
+ method = RequestMethod.PUT,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ public @ResponseBody ResponseEntity<Void> changeBalanceSegmentSet(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier,
+ @RequestBody @Valid BalanceSegmentSet balanceSegmentSet) {
+ checkThatProductAndBalanceSegmentSetExist(productIdentifier, balanceSegmentSetIdentifier);
+ checkProductChangeable(productIdentifier);
+
+ if (!balanceSegmentSetIdentifier.equals(balanceSegmentSet.getIdentifier()))
+ throw ServiceException.badRequest("Instance identifier may not be changed.");
+
+ this.commandGateway.process(new ChangeBalanceSegmentSetCommand(productIdentifier, balanceSegmentSet));
+ return new ResponseEntity<>(HttpStatus.ACCEPTED);
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
+ @RequestMapping(
+ value = "{balancesegmentsetidentifier}",
+ method = RequestMethod.DELETE,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public @ResponseBody ResponseEntity<Void> deleteBalanceSegmentSet(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier) {
+ checkThatProductAndBalanceSegmentSetExist(productIdentifier, balanceSegmentSetIdentifier);
+ checkProductChangeable(productIdentifier);
+
+ this.commandGateway.process(new DeleteBalanceSegmentSetCommand(productIdentifier, balanceSegmentSetIdentifier));
+ return new ResponseEntity<>(HttpStatus.ACCEPTED);
+ }
+
+ private void checkThatProductExists(final String productIdentifier) {
+ if (!productService.existsByIdentifier(productIdentifier))
+ throw ServiceException.notFound("Product with identifier ''{0}'' doesn''t exist.", productIdentifier);
+ }
+
+ private void checkThatSegmentSetDoesntExist(final String productIdentifier, final String segmentSetIdentifier) {
+ if (balanceSegmentSetService.existsByIdentifier(productIdentifier, segmentSetIdentifier))
+ throw ServiceException.notFound("Segment set with identifier ''{0}.{1}'' already exists.", productIdentifier, segmentSetIdentifier);
+ }
+
+ private void checkThatProductAndBalanceSegmentSetExist(final String productIdentifier, final String segmentSetIdentifier) {
+ if (!balanceSegmentSetService.existsByIdentifier(productIdentifier, segmentSetIdentifier))
+ throw ServiceException.notFound("Segment set with identifier ''{0}.{1}'' doesn''t exist.", productIdentifier, segmentSetIdentifier);
+ }
+
+ private void checkProductChangeable(final String productIdentifier) {
+ if (caseService.existsByProductIdentifier(productIdentifier))
+ throw ServiceException.conflict("Cases exist for product with the identifier ''{0}''. Product cannot be changed.", productIdentifier);
+ }
+}
diff --git a/service/src/main/resources/db/migrations/mariadb/V7__balance_segment_sets.sql b/service/src/main/resources/db/migrations/mariadb/V7__balance_segment_sets.sql
new file mode 100644
index 0000000..2cd41a5
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V7__balance_segment_sets.sql
@@ -0,0 +1,26 @@
+--
+-- Copyright 2017 Kuelap, Inc.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE TABLE bastet_p_balance_segs (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ seg_set_identifier VARCHAR(32) NOT NULL,
+ segment_identifier VARCHAR(32) NOT NULL,
+ product_id BIGINT NOT NULL,
+ lower_bound DECIMAL(19,4) NOT NULL,
+ CONSTRAINT bastet_p_balance_segs_pk PRIMARY KEY (id),
+ CONSTRAINT bastet_p_balance_segs_uq UNIQUE (product_id, seg_set_identifier, segment_identifier),
+ CONSTRAINT bastet_p_balance_segs_fk FOREIGN KEY (product_id) REFERENCES bastet_products (id)
+);
\ No newline at end of file