Implemented product deletion.
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
index b2868e5..c76e05a 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
@@ -93,6 +93,15 @@
final Product product);
@RequestMapping(
+ value = "/products/{productidentifier}",
+ method = RequestMethod.DELETE,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.CONFLICT, exception = ProductInUseException.class)
+ void deleteProduct(@PathVariable("productidentifier") final String productIdentifier);
+
+ @RequestMapping(
value = "/products/{productidentifier}/incompleteaccountassignments",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE,
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java b/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
index cb687ae..87ba261 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
@@ -25,6 +25,7 @@
String INITIALIZE = "initialize";
String POST_PRODUCT = "post-product";
String PUT_PRODUCT = "put-product";
+ String DELETE_PRODUCT = "delete-product";
String PUT_PRODUCT_ENABLE = "put-enable";
String POST_CASE = "post-case";
String PUT_CASE = "put-case";
@@ -36,6 +37,7 @@
String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
String SELECTOR_POST_PRODUCT = SELECTOR_NAME + " = '" + POST_PRODUCT + "'";
String SELECTOR_PUT_PRODUCT = SELECTOR_NAME + " = '" + PUT_PRODUCT + "'";
+ String SELECTOR_DELETE_PRODUCT = SELECTOR_NAME + " = '" + DELETE_PRODUCT + "'";
String SELECTOR_PUT_PRODUCT_ENABLE = SELECTOR_NAME + " = '" + PUT_PRODUCT_ENABLE + "'";
String SELECTOR_POST_CASE = SELECTOR_NAME + " = '" + POST_CASE + "'";
String SELECTOR_PUT_CASE = SELECTOR_NAME + " = '" + PUT_CASE + "'";
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestProducts.java b/component-test/src/main/java/io/mifos/portfolio/TestProducts.java
index 41160a4..2028e57 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestProducts.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestProducts.java
@@ -16,6 +16,7 @@
package io.mifos.portfolio;
import com.google.gson.Gson;
+import io.mifos.core.api.util.NotFoundException;
import io.mifos.core.test.domain.TimeStampChecker;
import io.mifos.individuallending.api.v1.domain.product.ProductParameters;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
@@ -271,6 +272,43 @@
Assert.assertFalse(portfolioManager.getProductEnabled(product.getIdentifier()));
}
+ @Test(expected = ProductInUseException.class)
+ public void shouldFailToDeleteProductAfterCaseHasBeenCreated() throws InterruptedException {
+ final Product product = createAndEnableProduct();
+
+ createCase(product.getIdentifier());
+
+ portfolioManager.deleteProduct(product.getIdentifier());
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void shouldFailToDeleteNonExistentProduct() throws InterruptedException {
+ portfolioManager.deleteProduct("habberdash");
+ }
+
+ @Test
+ public void shouldDeleteProduct() throws InterruptedException {
+ final Product product = createAndEnableProduct();
+
+ try {
+ portfolioManager.deleteProduct(product.getIdentifier());
+ Assert.fail("Product is enabled. It shouldn't be possible to delete it.");
+ }
+ catch (final ProductInUseException ignored) {}
+
+ portfolioManager.enableProduct(product.getIdentifier(), false);
+ Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
+
+ portfolioManager.deleteProduct(product.getIdentifier());
+ Assert.assertTrue(this.eventRecorder.wait(EventConstants.DELETE_PRODUCT, product.getIdentifier()));
+
+ try {
+ portfolioManager.getProduct(product.getIdentifier());
+ Assert.fail("product should not be found after delete.");
+ }
+ catch (final NotFoundException ignored) {}
+ }
+
private Product getTestProductWithMaximumLengthEverything()
{
final Product product = new Product();
diff --git a/component-test/src/main/java/io/mifos/portfolio/listener/ProductEventListener.java b/component-test/src/main/java/io/mifos/portfolio/listener/ProductEventListener.java
index 1e8b18b..1bc18bb 100644
--- a/component-test/src/main/java/io/mifos/portfolio/listener/ProductEventListener.java
+++ b/component-test/src/main/java/io/mifos/portfolio/listener/ProductEventListener.java
@@ -61,6 +61,16 @@
@JmsListener(
subscription = EventConstants.DESTINATION,
destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_DELETE_PRODUCT
+ )
+ public void onDeleteProduct(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.eventRecorder.event(tenant, EventConstants.DELETE_PRODUCT, payload, String.class);
+ }
+
+ @JmsListener(
+ subscription = EventConstants.DESTINATION,
+ destination = EventConstants.DESTINATION,
selector = EventConstants.SELECTOR_PUT_PRODUCT_ENABLE
)
public void onEnableProduct(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteProductCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteProductCommand.java
new file mode 100644
index 0000000..a333a75
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteProductCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * 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 DeleteProductCommand {
+ private final String productIdentifier;
+
+ public DeleteProductCommand(final String productIdentifier) {
+ this.productIdentifier = productIdentifier;
+ }
+
+ public String getProductIdentifier() {
+ return productIdentifier;
+ }
+
+ @Override
+ public String toString() {
+ return "DeleteProductCommand{" +
+ "productIdentifier='" + productIdentifier + '\'' +
+ '}';
+ }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
index e9de6d8..7173e76 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
@@ -27,6 +27,7 @@
import io.mifos.portfolio.service.internal.command.ChangeEnablingOfProductCommand;
import io.mifos.portfolio.service.internal.command.ChangeProductCommand;
import io.mifos.portfolio.service.internal.command.CreateProductCommand;
+import io.mifos.portfolio.service.internal.command.DeleteProductCommand;
import io.mifos.portfolio.service.internal.mapper.ChargeDefinitionMapper;
import io.mifos.portfolio.service.internal.mapper.ProductMapper;
import io.mifos.portfolio.service.internal.pattern.PatternFactoryRegistry;
@@ -110,6 +111,25 @@
@Transactional
@CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.DELETE_PRODUCT)
+ public String process(final DeleteProductCommand deleteProductCommand) {
+ final String productIdentifier = deleteProductCommand.getProductIdentifier();
+ final ProductEntity product = productRepository.findByIdentifier(productIdentifier)
+ .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier));
+
+ if (product.getEnabled())
+ throw ServiceException.conflict("Cannot delete product with identifier ''{0}'', because it is enabled.", productIdentifier);
+
+ if (caseRepository.existsByProductIdentifier(productIdentifier))
+ throw ServiceException.conflict("Cannot delete product with identifier ''{0}'', because there are already cases defined on it.", productIdentifier);
+
+ productRepository.delete(product);
+
+ return deleteProductCommand.getProductIdentifier();
+ }
+
+ @Transactional
+ @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
@EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.PUT_PRODUCT_ENABLE)
public String process(final ChangeEnablingOfProductCommand changeEnablingOfProductCommand)
{
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
index b6e02cd..e571632 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
@@ -149,6 +149,8 @@
return newAccountAssignmentEntity;
}).collect(Collectors.toSet());
+ newEntity.setChargeDefinitions(oldEntity.getChargeDefinitions());
+ newEntity.setTaskDefinitions(oldEntity.getTaskDefinitions());
newEntity.setAccountAssignments(newAccountAssignmentEntities);
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
index a4d7c87..92c436c 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
@@ -37,7 +37,7 @@
private String identifier;
@ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "product_id")
+ @JoinColumn(name = "product_id", nullable = false)
private ProductEntity product;
@Column(name = "a_name")
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
index da0196f..d13d6fc 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ProductEntity.java
@@ -87,6 +87,12 @@
@OneToMany(targetEntity = ProductAccountAssignmentEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product")
private Set<ProductAccountAssignmentEntity> accountAssignments;
+ @OneToMany(targetEntity = ChargeDefinitionEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product")
+ private Set<ChargeDefinitionEntity> chargeDefinitions;
+
+ @OneToMany(targetEntity = TaskDefinitionEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "product")
+ private Set<TaskDefinitionEntity> taskDefinitions;
+
@Column(name = "parameters")
private String parameters;
@@ -246,6 +252,22 @@
this.accountAssignments = accountAssignments;
}
+ public Set<ChargeDefinitionEntity> getChargeDefinitions() {
+ return chargeDefinitions;
+ }
+
+ public void setChargeDefinitions(Set<ChargeDefinitionEntity> chargeDefinitions) {
+ this.chargeDefinitions = chargeDefinitions;
+ }
+
+ public Set<TaskDefinitionEntity> getTaskDefinitions() {
+ return taskDefinitions;
+ }
+
+ public void setTaskDefinitions(Set<TaskDefinitionEntity> taskDefinitions) {
+ this.taskDefinitions = taskDefinitions;
+ }
+
public String getParameters() {
return parameters;
}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
index 1db14aa..ca8e4bc 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/TaskDefinitionEntity.java
@@ -34,7 +34,7 @@
private String identifier;
@ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "product_id")
+ @JoinColumn(name = "product_id", nullable = false)
private ProductEntity product;
@Column(name = "a_name")
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/ProductRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/ProductRestController.java
index fe117f7..569161b 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/ProductRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/ProductRestController.java
@@ -31,6 +31,7 @@
import io.mifos.portfolio.service.internal.command.ChangeEnablingOfProductCommand;
import io.mifos.portfolio.service.internal.command.ChangeProductCommand;
import io.mifos.portfolio.service.internal.command.CreateProductCommand;
+import io.mifos.portfolio.service.internal.command.DeleteProductCommand;
import io.mifos.portfolio.service.internal.service.CaseService;
import io.mifos.portfolio.service.internal.service.PatternService;
import io.mifos.portfolio.service.internal.service.ProductService;
@@ -150,6 +151,28 @@
return ResponseEntity.accepted().build();
}
+ @RequestMapping(
+ value = "/{productidentifier}",
+ method = RequestMethod.DELETE,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public @ResponseBody ResponseEntity<Void> deleteProduct(@PathVariable("productidentifier") final String productIdentifier)
+ {
+ final boolean enabled = productService.findEnabledByIdentifier(productIdentifier)
+ .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier));
+
+ if (enabled)
+ throw ServiceException.conflict("Cannot delete product with identifier ''{0}'', because it is enabled.", productIdentifier);
+
+ if (caseService.existsByProductIdentifier(productIdentifier))
+ throw ServiceException.conflict("Cannot delete product with identifier ''{0}'', because there are already cases defined on it.", productIdentifier);
+
+ commandGateway.process(new DeleteProductCommand(productIdentifier));
+
+ return ResponseEntity.accepted().build();
+ }
+
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_OPERATIONS_MANAGEMENT)
@RequestMapping(
value = "/{productidentifier}/incompleteaccountassignments",
@@ -159,7 +182,7 @@
public @ResponseBody ResponseEntity<Set<AccountAssignment>> getIncompleteAccountAssignments(@PathVariable("productidentifier") final String productIdentifier)
{
productService.findByIdentifier(productIdentifier)
- .orElseThrow(() -> ServiceException.notFound("Instance with identifier " + productIdentifier + " doesn't exist."));
+ .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier));
return ResponseEntity.ok(productService.getIncompleteAccountAssignments(productIdentifier));
}
@@ -175,7 +198,7 @@
@RequestBody final Boolean enabled)
{
productService.findByIdentifier(productIdentifier)
- .orElseThrow(() -> ServiceException.notFound("Instance with identifier " + productIdentifier + " doesn't exist."));
+ .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier));
if (enabled) {
if (!productService.areChargeDefinitionsCoveredByAccountAssignments(productIdentifier))
@@ -197,7 +220,7 @@
public @ResponseBody ResponseEntity<Boolean> getProductEnabled(@PathVariable("productidentifier") final String productIdentifier)
{
return ResponseEntity.ok(productService.findEnabledByIdentifier(productIdentifier)
- .orElseThrow(() -> ServiceException.notFound("Instance with identifier " + productIdentifier + " doesn't exist.")));
+ .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''{0}'' doesn''t exist.", productIdentifier)));
}
}