Merge pull request #43 from myrle-krantz/develop
Charges necessary for the functioning of loans are now readonly.
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/ChargeDefinitionIsReadOnly.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/ChargeDefinitionIsReadOnly.java
new file mode 100644
index 0000000..e374850
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/client/ChargeDefinitionIsReadOnly.java
@@ -0,0 +1,22 @@
+/*
+ * 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.api.v1.client;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChargeDefinitionIsReadOnly extends RuntimeException {
+}
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 1b2f61e..b80fa76 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
@@ -208,6 +208,7 @@
produces = MediaType.ALL_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
+ @ThrowsException(status = HttpStatus.CONFLICT, exception = ChargeDefinitionIsReadOnly.class)
void changeChargeDefinition(
@PathVariable("productidentifier") final String productIdentifier,
@PathVariable("chargedefinitionidentifier") final String chargeDefinitionIdentifier,
@@ -219,6 +220,7 @@
produces = MediaType.ALL_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
+ @ThrowsException(status = HttpStatus.CONFLICT, exception = ChargeDefinitionIsReadOnly.class)
void deleteChargeDefinition(
@PathVariable("productidentifier") final String productIdentifier,
@PathVariable("chargedefinitionidentifier") final String chargeDefinitionIdentifier);
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
index 8cd4fc0..2268871 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
@@ -79,6 +79,8 @@
@ValidPaymentCycleUnit
private ChronoUnit forCycleSizeUnit;
+ private boolean readOnly;
+
public ChargeDefinition() {
}
@@ -179,45 +181,55 @@
this.forCycleSizeUnit = forCycleSizeUnit;
}
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChargeDefinition that = (ChargeDefinition) o;
- return Objects.equals(identifier, that.identifier) &&
- Objects.equals(name, that.name) &&
- Objects.equals(description, that.description) &&
- Objects.equals(accrueAction, that.accrueAction) &&
- Objects.equals(chargeAction, that.chargeAction) &&
- Objects.equals(amount, that.amount) &&
- chargeMethod == that.chargeMethod &&
- Objects.equals(proportionalTo, that.proportionalTo) &&
- Objects.equals(fromAccountDesignator, that.fromAccountDesignator) &&
- Objects.equals(accrualAccountDesignator, that.accrualAccountDesignator) &&
- Objects.equals(toAccountDesignator, that.toAccountDesignator) &&
- forCycleSizeUnit == that.forCycleSizeUnit;
+ return readOnly == that.readOnly &&
+ Objects.equals(identifier, that.identifier) &&
+ Objects.equals(name, that.name) &&
+ Objects.equals(description, that.description) &&
+ Objects.equals(accrueAction, that.accrueAction) &&
+ Objects.equals(chargeAction, that.chargeAction) &&
+ Objects.equals(amount, that.amount) &&
+ chargeMethod == that.chargeMethod &&
+ Objects.equals(proportionalTo, that.proportionalTo) &&
+ Objects.equals(fromAccountDesignator, that.fromAccountDesignator) &&
+ Objects.equals(accrualAccountDesignator, that.accrualAccountDesignator) &&
+ Objects.equals(toAccountDesignator, that.toAccountDesignator) &&
+ forCycleSizeUnit == that.forCycleSizeUnit;
}
@Override
public int hashCode() {
- return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit);
+ return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit, readOnly);
}
@Override
public String toString() {
return "ChargeDefinition{" +
- "identifier='" + identifier + '\'' +
- ", name='" + name + '\'' +
- ", description='" + description + '\'' +
- ", accrueAction='" + accrueAction + '\'' +
- ", chargeAction='" + chargeAction + '\'' +
- ", amount=" + amount +
- ", chargeMethod=" + chargeMethod +
- ", proportionalTo='" + proportionalTo + '\'' +
- ", fromAccountDesignator='" + fromAccountDesignator + '\'' +
- ", accrualAccountDesignator='" + accrualAccountDesignator + '\'' +
- ", toAccountDesignator='" + toAccountDesignator + '\'' +
- ", forCycleSizeUnit=" + forCycleSizeUnit +
- '}';
+ "identifier='" + identifier + '\'' +
+ ", name='" + name + '\'' +
+ ", description='" + description + '\'' +
+ ", accrueAction='" + accrueAction + '\'' +
+ ", chargeAction='" + chargeAction + '\'' +
+ ", amount=" + amount +
+ ", chargeMethod=" + chargeMethod +
+ ", proportionalTo='" + proportionalTo + '\'' +
+ ", fromAccountDesignator='" + fromAccountDesignator + '\'' +
+ ", accrualAccountDesignator='" + accrualAccountDesignator + '\'' +
+ ", toAccountDesignator='" + toAccountDesignator + '\'' +
+ ", forCycleSizeUnit=" + forCycleSizeUnit +
+ ", readOnly=" + readOnly +
+ '}';
}
}
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
index 59f3c27..f65e8a6 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
@@ -16,8 +16,11 @@
package io.mifos.portfolio;
import io.mifos.core.api.util.NotFoundException;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.portfolio.api.v1.client.ChargeDefinitionIsReadOnly;
import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
import io.mifos.portfolio.api.v1.domain.Product;
import io.mifos.portfolio.api.v1.events.ChargeDefinitionEvent;
@@ -27,6 +30,7 @@
import java.math.BigDecimal;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -40,34 +44,58 @@
final Product product = createProduct();
final List<ChargeDefinition> charges = portfolioManager.getAllChargeDefinitionsForProduct(product.getIdentifier());
- final Set<String> chargeDefinitionIdentifiers = charges.stream().map(ChargeDefinition::getIdentifier).collect(Collectors.toSet());
- final Set<String> expectedChargeDefinitionIdentifiers = Stream.of(
+ final Map<Boolean, List<ChargeDefinition>> chargeIdentifersPartitionedByReadOnly
+ = charges.stream().collect(Collectors.partitioningBy(ChargeDefinition::isReadOnly, Collectors.toList()));
+ final Set<String> readOnlyChargeDefinitionIdentifiers
+ = chargeIdentifersPartitionedByReadOnly.get(true).stream()
+ .map(ChargeDefinition::getIdentifier)
+ .collect(Collectors.toSet());
+ final Set<String> changeableChargeDefinitionIdentifiers
+ = chargeIdentifersPartitionedByReadOnly.get(false).stream()
+ .map(ChargeDefinition::getIdentifier)
+ .collect(Collectors.toSet());
+
+ final Set<String> expectedReadOnlyChargeDefinitionIdentifiers = Stream.of(
ChargeIdentifiers.ALLOW_FOR_WRITE_OFF_ID,
- ChargeIdentifiers.DISBURSEMENT_FEE_ID,
- ChargeIdentifiers.INTEREST_ID,
- ChargeIdentifiers.LATE_FEE_ID,
ChargeIdentifiers.LOAN_FUNDS_ALLOCATION_ID,
- ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID,
- ChargeIdentifiers.PROCESSING_FEE_ID,
ChargeIdentifiers.RETURN_DISBURSEMENT_ID,
ChargeIdentifiers.DISBURSE_PAYMENT_ID,
ChargeIdentifiers.TRACK_DISBURSAL_PAYMENT_ID,
ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID,
- ChargeIdentifiers.REPAYMENT_ID
- )
+ ChargeIdentifiers.REPAYMENT_ID)
.collect(Collectors.toSet());
- Assert.assertEquals(expectedChargeDefinitionIdentifiers, chargeDefinitionIdentifiers);
+ final Set<String> expectedChangeableChargeDefinitionIdentifiers = Stream.of(
+ ChargeIdentifiers.DISBURSEMENT_FEE_ID,
+ ChargeIdentifiers.INTEREST_ID,
+ ChargeIdentifiers.LATE_FEE_ID,
+ ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID,
+ ChargeIdentifiers.PROCESSING_FEE_ID)
+ .collect(Collectors.toSet());
+
+ Assert.assertEquals(expectedReadOnlyChargeDefinitionIdentifiers, readOnlyChargeDefinitionIdentifiers);
+ Assert.assertEquals(expectedChangeableChargeDefinitionIdentifiers, changeableChargeDefinitionIdentifiers);
}
@Test
public void shouldDeleteChargeDefinition() throws InterruptedException {
final Product product = createProduct();
- final List<ChargeDefinition> charges = portfolioManager.getAllChargeDefinitionsForProduct(product.getIdentifier());
- final ChargeDefinition chargeDefinitionToDelete = charges.get(0);
+ final ChargeDefinition chargeDefinitionToDelete = new ChargeDefinition();
+ chargeDefinitionToDelete.setAmount(BigDecimal.TEN);
+ chargeDefinitionToDelete.setIdentifier("blah");
+ chargeDefinitionToDelete.setName("blah blah");
+ chargeDefinitionToDelete.setDescription("blah blah blah");
+ chargeDefinitionToDelete.setChargeAction(Action.APPROVE.name());
+ chargeDefinitionToDelete.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+ chargeDefinitionToDelete.setToAccountDesignator(AccountDesignators.ARREARS_ALLOWANCE);
+ chargeDefinitionToDelete.setFromAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
+ portfolioManager.createChargeDefinition(product.getIdentifier(), chargeDefinitionToDelete);
+ Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_CHARGE_DEFINITION,
+ new ChargeDefinitionEvent(product.getIdentifier(), chargeDefinitionToDelete.getIdentifier())));
+
portfolioManager.deleteChargeDefinition(product.getIdentifier(), chargeDefinitionToDelete.getIdentifier());
Assert.assertTrue(this.eventRecorder.wait(EventConstants.DELETE_PRODUCT_CHARGE_DEFINITION,
- new ChargeDefinitionEvent(product.getIdentifier(), chargeDefinitionToDelete.getIdentifier())));
+ new ChargeDefinitionEvent(product.getIdentifier(), chargeDefinitionToDelete.getIdentifier())));
try {
portfolioManager.getChargeDefinition(product.getIdentifier(), chargeDefinitionToDelete.getIdentifier());
@@ -77,6 +105,13 @@
catch (final NotFoundException ignored) { }
}
+ @Test(expected = ChargeDefinitionIsReadOnly.class)
+ public void shouldNotDeleteReadOnlyChargeDefinition() throws InterruptedException {
+ final Product product = createProduct();
+
+ portfolioManager.deleteChargeDefinition(product.getIdentifier(), ChargeIdentifiers.ALLOW_FOR_WRITE_OFF_ID);
+ }
+
@Test
public void shouldCreateChargeDefinition() throws InterruptedException {
final Product product = createProduct();
@@ -120,4 +155,31 @@
Assert.assertEquals(interestChargeDefinition, chargeDefinitionAsChanged);
}
+
+ @Test
+ public void shouldNotChangeDisbursalChargeDefinition() throws InterruptedException {
+ final Product product = createProduct();
+
+ final ChargeDefinition originalDisbursalChargeDefinition
+ = portfolioManager.getChargeDefinition(product.getIdentifier(), ChargeIdentifiers.DISBURSE_PAYMENT_ID);
+
+ final ChargeDefinition disbursalChargeDefinition
+ = portfolioManager.getChargeDefinition(product.getIdentifier(), ChargeIdentifiers.DISBURSE_PAYMENT_ID);
+ disbursalChargeDefinition.setProportionalTo(ChargeProportionalDesignator.NOT_PROPORTIONAL.getValue());
+ disbursalChargeDefinition.setReadOnly(false);
+
+ try {
+ portfolioManager.changeChargeDefinition(
+ product.getIdentifier(),
+ disbursalChargeDefinition.getIdentifier(),
+ disbursalChargeDefinition);
+ Assert.fail("Changing a readonly charge definition should fail.");
+ }
+ catch (final ChargeDefinitionIsReadOnly ignore) { }
+
+ final ChargeDefinition chargeDefinitionAsChanged
+ = portfolioManager.getChargeDefinition(product.getIdentifier(), disbursalChargeDefinition.getIdentifier());
+
+ Assert.assertEquals(originalDisbursalChargeDefinition, chargeDefinitionAsChanged);
+ }
}
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index ed4c8a0..3de3a47 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -106,6 +106,7 @@
BigDecimal.valueOf(0.01),
ENTRY,
PROCESSING_FEE_INCOME);
+ processingFee.setReadOnly(false);
final ChargeDefinition loanOriginationFee = charge(
LOAN_ORIGINATION_FEE_NAME,
@@ -113,6 +114,7 @@
BigDecimal.valueOf(0.01),
ENTRY,
ORIGINATION_FEE_INCOME);
+ loanOriginationFee.setReadOnly(false);
final ChargeDefinition loanFundsAllocation = charge(
LOAN_FUNDS_ALLOCATION_ID,
@@ -120,6 +122,7 @@
BigDecimal.valueOf(1.00),
LOAN_FUNDS_SOURCE,
PENDING_DISBURSAL);
+ loanFundsAllocation.setReadOnly(true);
final ChargeDefinition disbursementFee = charge(
DISBURSEMENT_FEE_NAME,
@@ -127,6 +130,7 @@
BigDecimal.valueOf(0.001),
ENTRY,
DISBURSEMENT_FEE_INCOME);
+ disbursementFee.setReadOnly(false);
final ChargeDefinition disbursePayment = new ChargeDefinition();
disbursePayment.setChargeAction(Action.DISBURSE.name());
@@ -138,6 +142,7 @@
disbursePayment.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
disbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
disbursePayment.setAmount(BigDecimal.ONE);
+ disbursePayment.setReadOnly(true);
final ChargeDefinition trackPrincipalDisbursePayment = new ChargeDefinition();
trackPrincipalDisbursePayment.setChargeAction(Action.DISBURSE.name());
@@ -149,6 +154,7 @@
trackPrincipalDisbursePayment.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
trackPrincipalDisbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
trackPrincipalDisbursePayment.setAmount(BigDecimal.ONE);
+ trackPrincipalDisbursePayment.setReadOnly(true);
//TODO: Make payable at time of ACCEPT_PAYMENT but accrued at MARK_LATE
final ChargeDefinition lateFee = charge(
@@ -160,6 +166,7 @@
lateFee.setAccrueAction(Action.MARK_LATE.name());
lateFee.setAccrualAccountDesignator(LATE_FEE_ACCRUAL);
lateFee.setProportionalTo(ChargeIdentifiers.REPAYMENT_ID);
+ lateFee.setReadOnly(false);
//TODO: Make multiple write off allowance charges.
final ChargeDefinition writeOffAllowanceCharge = charge(
@@ -169,6 +176,7 @@
PENDING_DISBURSAL,
ARREARS_ALLOWANCE);
writeOffAllowanceCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
+ writeOffAllowanceCharge.setReadOnly(true);
final ChargeDefinition interestCharge = charge(
INTEREST_NAME,
@@ -180,6 +188,7 @@
interestCharge.setAccrueAction(Action.APPLY_INTEREST.name());
interestCharge.setAccrualAccountDesignator(INTEREST_ACCRUAL);
interestCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
+ interestCharge.setReadOnly(false);
final ChargeDefinition customerRepaymentCharge = new ChargeDefinition();
customerRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
@@ -191,6 +200,7 @@
customerRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue());
customerRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
customerRepaymentCharge.setAmount(BigDecimal.ONE);
+ customerRepaymentCharge.setReadOnly(true);
final ChargeDefinition trackReturnPrincipalCharge = new ChargeDefinition();
trackReturnPrincipalCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
@@ -202,6 +212,7 @@
trackReturnPrincipalCharge.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
trackReturnPrincipalCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
trackReturnPrincipalCharge.setAmount(BigDecimal.ONE);
+ trackReturnPrincipalCharge.setReadOnly(true);
final ChargeDefinition disbursementReturnCharge = charge(
RETURN_DISBURSEMENT_NAME,
@@ -209,7 +220,8 @@
BigDecimal.valueOf(1.0),
PENDING_DISBURSAL,
LOAN_FUNDS_SOURCE);
- interestCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
+ disbursementReturnCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
+ disbursementReturnCharge.setReadOnly(true);
ret.add(processingFee);
ret.add(loanOriginationFee);
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
index 4718e1b..91958f5 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
@@ -20,6 +20,8 @@
import io.mifos.portfolio.service.internal.repository.ChargeDefinitionEntity;
import io.mifos.portfolio.service.internal.repository.ProductEntity;
+import java.util.Optional;
+
import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
/**
@@ -43,6 +45,7 @@
ret.setFromAccountDesignator(chargeDefinition.getFromAccountDesignator());
ret.setAccrualAccountDesignator(chargeDefinition.getAccrualAccountDesignator());
ret.setToAccountDesignator(chargeDefinition.getToAccountDesignator());
+ ret.setReadOnly(chargeDefinition.isReadOnly());
return ret;
}
@@ -62,10 +65,42 @@
ret.setFromAccountDesignator(from.getFromAccountDesignator());
ret.setAccrualAccountDesignator(from.getAccrualAccountDesignator());
ret.setToAccountDesignator(from.getToAccountDesignator());
+ ret.setReadOnly(Optional.ofNullable(from.getReadOnly()).orElseGet(() -> readOnlyLegacyMapper(from.getIdentifier())));
return ret;
}
+ private static Boolean readOnlyLegacyMapper(final String identifier) {
+ switch (identifier) {
+ case LOAN_FUNDS_ALLOCATION_ID:
+ return true;
+ case RETURN_DISBURSEMENT_ID:
+ return true;
+ case INTEREST_ID:
+ return false;
+ case ALLOW_FOR_WRITE_OFF_ID:
+ return false;
+ case LATE_FEE_ID:
+ return true;
+ case DISBURSEMENT_FEE_ID:
+ return false;
+ case DISBURSE_PAYMENT_ID:
+ return false;
+ case TRACK_DISBURSAL_PAYMENT_ID:
+ return false;
+ case LOAN_ORIGINATION_FEE_ID:
+ return true;
+ case PROCESSING_FEE_ID:
+ return true;
+ case REPAYMENT_ID:
+ return false;
+ case TRACK_RETURN_PRINCIPAL_ID:
+ return false;
+ default:
+ return false;
+ }
+ }
+
private static String proportionalToLegacyMapper(final ChargeDefinitionEntity from,
final ChargeDefinition.ChargeMethod chargeMethod,
final String identifier) {
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 3f0eab0..423c871 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
@@ -76,6 +76,9 @@
@Column(name = "for_cycle_size_unit")
private ChronoUnit forCycleSizeUnit;
+ @Column(name = "read_only")
+ private Boolean readOnly;
+
public ChargeDefinitionEntity() {
}
@@ -191,6 +194,14 @@
this.forCycleSizeUnit = forCycleSizeUnit;
}
+ public Boolean getReadOnly() {
+ return readOnly;
+ }
+
+ public void setReadOnly(Boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/ChargeDefinitionRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/ChargeDefinitionRestController.java
index d460ea7..9e74ff2 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/ChargeDefinitionRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/ChargeDefinitionRestController.java
@@ -84,8 +84,11 @@
{
checkProductExists(productIdentifier);
+ if (instance.isReadOnly())
+ throw ServiceException.badRequest("Created charges cannot be read only.");
+
chargeDefinitionService.findByIdentifier(productIdentifier, instance.getIdentifier())
- .ifPresent(taskDefinition -> {throw ServiceException.conflict("Duplicate identifier: " + taskDefinition.getIdentifier());});
+ .ifPresent(taskDefinition -> {throw ServiceException.conflict("Duplicate identifier: " + taskDefinition.getIdentifier());});
this.commandGateway.process(new CreateChargeDefinitionCommand(productIdentifier, instance));
return new ResponseEntity<>(HttpStatus.ACCEPTED);
@@ -105,7 +108,7 @@
checkProductExists(productIdentifier);
return chargeDefinitionService.findByIdentifier(productIdentifier, chargeDefinitionIdentifier).orElseThrow(
- () -> ServiceException.notFound("No charge definition with the identifier '" + chargeDefinitionIdentifier + "' found."));
+ () -> ServiceException.notFound("No charge definition with the identifier '" + chargeDefinitionIdentifier + "' found."));
}
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
@@ -120,7 +123,7 @@
@PathVariable("chargedefinitionidentifier") final String chargeDefinitionIdentifier,
@RequestBody @Valid final ChargeDefinition instance)
{
- checkChargeExistsInProduct(productIdentifier, chargeDefinitionIdentifier);
+ checkChargeExistsInProductAndIsNotReadOnly(productIdentifier, chargeDefinitionIdentifier);
if (!chargeDefinitionIdentifier.equals(instance.getIdentifier()))
throw ServiceException.badRequest("Instance identifiers may not be changed.");
@@ -141,17 +144,22 @@
@PathVariable("productidentifier") final String productIdentifier,
@PathVariable("chargedefinitionidentifier") final String chargeDefinitionIdentifier)
{
- checkChargeExistsInProduct(productIdentifier, chargeDefinitionIdentifier);
+ checkChargeExistsInProductAndIsNotReadOnly(productIdentifier, chargeDefinitionIdentifier);
commandGateway.process(new DeleteProductChargeDefinitionCommand(productIdentifier, chargeDefinitionIdentifier));
return ResponseEntity.accepted().build();
}
- private void checkChargeExistsInProduct(final String productIdentifier,
- final String chargeDefinitionIdentifier) {
- chargeDefinitionService.findByIdentifier(productIdentifier, chargeDefinitionIdentifier).orElseThrow(
- () -> ServiceException.notFound("No charge definition in the product " + productIdentifier + "with the identifier '" + chargeDefinitionIdentifier + "' found."));
+ private void checkChargeExistsInProductAndIsNotReadOnly(final String productIdentifier,
+ final String chargeDefinitionIdentifier) {
+ final boolean readOnly = chargeDefinitionService.findByIdentifier(productIdentifier, chargeDefinitionIdentifier)
+ .orElseThrow(() -> ServiceException.notFound("No charge definition ''{0}.{1}'' found.",
+ productIdentifier, chargeDefinitionIdentifier))
+ .isReadOnly();
+
+ if (readOnly)
+ throw ServiceException.conflict("Charge definition is read only ''{0}''", chargeDefinitionIdentifier);
}
private void checkProductExists(final String productIdentifier) {
diff --git a/service/src/main/resources/db/migrations/mariadb/V5__readonly_charges.sql b/service/src/main/resources/db/migrations/mariadb/V5__readonly_charges.sql
new file mode 100644
index 0000000..7e8856d
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V5__readonly_charges.sql
@@ -0,0 +1,17 @@
+--
+-- 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.
+--
+
+ALTER TABLE bastet_p_chrg_defs ADD COLUMN read_only BOOLEAN NULL DEFAULT NULL;
\ No newline at end of file