diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java index 0cb64e568..fb2f1c8a3 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java @@ -24,6 +24,7 @@ import org.apache.commons.lang.StringUtils; import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand; import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand; +import org.mifosng.platform.api.commands.LoanChargeCommand; import org.mifosng.platform.api.commands.LoanStateTransitionCommand; import org.mifosng.platform.api.commands.LoanTransactionCommand; import org.mifosng.platform.api.commands.UndoStateTransitionCommand; @@ -481,4 +482,49 @@ public class LoansApiResource { return Response.ok().entity(identifier).build(); } + + @POST + @Path("{loanId}/charges") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public Response addLoanCharge( + @PathParam("loanId") final Long loanId, + final String jsonRequestBody){ + + LoanChargeCommand command = this.apiDataConversionService.convertJsonToLoanChargeCommand(null, loanId, jsonRequestBody); + + EntityIdentifier identifier = this.loanWritePlatformService.addLoanCharge(command); + + return Response.ok().entity(identifier).build(); + } + + @PUT + @Path("{loanId}/charges/{chargeId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public Response updateLoanCharge( + @PathParam("loanId") final Long loanId, + @PathParam("chargeId") final Long loanChargeId, + final String jsonRequestBody){ + + LoanChargeCommand command = this.apiDataConversionService.convertJsonToLoanChargeCommand(loanChargeId, loanId, jsonRequestBody); + + EntityIdentifier identifier = this.loanWritePlatformService.updateLoanCharge(command); + + return Response.ok().entity(identifier).build(); + } + + @DELETE + @Path("{loanId}/charges/{chargeId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public Response deleteLoanCharge( + @PathParam("loanId") final Long loanId, + @PathParam("chargeId") final Long loanChargeId){ + + EntityIdentifier identifier = this.loanWritePlatformService.deleteLoanCharge(loanId, loanChargeId); + + return Response.ok().entity(identifier).build(); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanChargeCommand.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanChargeCommand.java index 0c4d22e9f..bbe184aeb 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanChargeCommand.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanChargeCommand.java @@ -7,6 +7,7 @@ public class LoanChargeCommand { private final Long id; private final Long chargeId; + private final Long loanId; private final BigDecimal amount; private final Integer chargeTimeType; @@ -14,11 +15,11 @@ public class LoanChargeCommand { private final Set modifiedParameters; - public LoanChargeCommand(final Set modifiedParameters, final Long id, final Long chargeId, - final BigDecimal amount, final Integer chargeTimeType, final Integer chargeCalculationType) { + public LoanChargeCommand(Set modifiedParameters, Long id, Long loanId, Long chargeId, BigDecimal amount, Integer chargeTimeType, Integer chargeCalculationType) { this.modifiedParameters = modifiedParameters; this.id = id; this.chargeId = chargeId; + this.loanId = loanId; this.amount = amount; this.chargeTimeType = chargeTimeType; this.chargeCalculationType = chargeCalculationType; @@ -32,6 +33,10 @@ public class LoanChargeCommand { return chargeId; } + public Long getLoanId() { + return loanId; + } + public BigDecimal getAmount() { return amount; } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionService.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionService.java index 01530ccb7..cb9558cdb 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionService.java @@ -11,6 +11,7 @@ import org.mifosng.platform.api.commands.DepositStateTransitionApprovalCommand; import org.mifosng.platform.api.commands.DepositStateTransitionCommand; import org.mifosng.platform.api.commands.FundCommand; import org.mifosng.platform.api.commands.GroupCommand; +import org.mifosng.platform.api.commands.LoanChargeCommand; import org.mifosng.platform.api.commands.LoanProductCommand; import org.mifosng.platform.api.commands.LoanStateTransitionCommand; import org.mifosng.platform.api.commands.LoanTransactionCommand; @@ -49,6 +50,8 @@ public interface ApiDataConversionService { LoanApplicationCommand convertJsonToLoanApplicationCommand(Long resourceIdentifier, String jsonRequestBody); + LoanChargeCommand convertJsonToLoanChargeCommand(Long loanChargeId, Long loanId, String jsonRequestBody); + LoanStateTransitionCommand convertJsonToLoanStateTransitionCommand(Long resourceIdentifier, String jsonRequestBody); LoanTransactionCommand convertJsonToLoanTransactionCommand(Long resourceIdentifier, String jsonRequestBody); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java index 1c5bf5555..08e2dd90b 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java @@ -506,7 +506,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { Integer chargeTimeType = extractIntegerParameter("chargeTimeType", chargeRequestMap, chargeModifiedParameters); Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", chargeRequestMap, chargeModifiedParameters); - charges[i] = new LoanChargeCommand(chargeModifiedParameters, null, chargeId, amount, + charges[i] = new LoanChargeCommand(chargeModifiedParameters, null, null,chargeId, amount, chargeTimeType, chargeCalculationType); } } @@ -521,8 +521,34 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { loanTermFrequency, loanTermFrequencyType, inArrearsToleranceValue, charges,loanOfficerId); } - - @Override + + @Override + public LoanChargeCommand convertJsonToLoanChargeCommand(Long loanChargeId, Long loanId, String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + Type typeOfMap = new TypeToken>(){}.getType(); + Map requestMap = gsonConverter.fromJson(json, typeOfMap); + + Set supportedParams = new HashSet( + Arrays.asList("chargeId", "amount", "chargeTimeType", "chargeCalculationType", "locale") + ); + + checkForUnsupportedParameters(requestMap, supportedParams); + + Set modifiedParameters = new HashSet(); + + Long chargeId = extractLongParameter("chargeId", requestMap, modifiedParameters); + BigDecimal amount = extractBigDecimalParameter("amount", requestMap, modifiedParameters); + Integer chargeTimeType = extractIntegerParameter("chargeTimeType", requestMap, modifiedParameters); + Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", requestMap, modifiedParameters); + + return new LoanChargeCommand(modifiedParameters, loanChargeId, loanId, chargeId, amount, + chargeTimeType, chargeCalculationType); + } + + @Override public LoanStateTransitionCommand convertJsonToLoanStateTransitionCommand(final Long resourceIdentifier, final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java index d789c4731..d6d26f665 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/Loan.java @@ -577,6 +577,10 @@ public class Loan extends AbstractAuditableCustom { doPostLoanTransactionChecks(loanTransaction, loanLifecycleStateMachine); } + public void updateTotalChargesDueAtDisbursement(){ + this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); + } + public void makeRepayment(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) { handleRepaymentTransaction(loanTransaction, loanLifecycleStateMachine, false); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanCharge.java index b8b96a237..2992c4286 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanCharge.java @@ -38,7 +38,11 @@ public class LoanCharge extends AbstractPersistable { @Column(name = "charge_calculation_enum") private Integer chargeCalculation; - + + public static LoanCharge createNew(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) { + return new LoanCharge(loan, chargeDefinition, command); + } + public static LoanCharge createNew(final Charge chargeDefinition, final LoanChargeCommand command) { return new LoanCharge(null, chargeDefinition, command); } @@ -90,6 +94,22 @@ public class LoanCharge extends AbstractPersistable { this.loan = loan; } + public void update(final LoanChargeCommand command){ + + if (command.isAmountChanged()) { + this.amount = command.getAmount(); + } + + if (command.isChargeTimeTypeChanged()){ + this.chargeTime = ChargeTimeType.fromInt(command.getChargeTimeType()).getValue(); + } + + if (command.isChargeCalculationTypeChanged()){ + this.chargeCalculation = ChargeCalculationType.fromInt(command.getChargeCalculationType()).getValue(); + } + + } + public boolean isDueAtDisbursement() { return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT); } @@ -122,6 +142,7 @@ public class LoanCharge extends AbstractPersistable { public LoanChargeCommand toData() { Set modifiedParameters = new HashSet(); - return new LoanChargeCommand(modifiedParameters, this.getId(), this.charge.getId(), this.amount, this.chargeTime, this.chargeCalculation); + return new LoanChargeCommand(modifiedParameters, this.getId(), this.loan.getId(), this.charge.getId(), + this.amount, this.chargeTime, this.chargeCalculation); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanChargeRepository.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanChargeRepository.java new file mode 100644 index 000000000..593356fc0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanChargeRepository.java @@ -0,0 +1,8 @@ +package org.mifosng.platform.loan.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface LoanChargeRepository extends JpaRepository, JpaSpecificationExecutor { + // no added behaviour +} diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java index 5abd22dc0..eda57dfcf 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java @@ -58,7 +58,7 @@ public class LoanApplicationCommandValidator { for (LoanChargeCommand loanChargeCommand : this.command.getCharges()){ try { LoanChargeCommandValidator validator = new LoanChargeCommandValidator(loanChargeCommand); - validator.validate(); + validator.validateForCreate(); } catch (PlatformApiDataValidationException e) { dataValidationErrors.addAll(e.getErrors()); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanChargeCommandValidator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanChargeCommandValidator.java index 9d209674b..7aed9425c 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanChargeCommandValidator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanChargeCommandValidator.java @@ -17,7 +17,7 @@ public class LoanChargeCommandValidator { this.command = command; } - public void validate(){ + public void validateForCreate(){ List dataValidationErrors = new ArrayList(); DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge"); @@ -31,4 +31,18 @@ public class LoanChargeCommandValidator { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); } } + + public void validateForUpdate(){ + List dataValidationErrors = new ArrayList(); + + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge"); + + baseDataValidator.reset().parameter("amount").value(command.getAmount()).ignoreIfNull().positiveAmount(); + baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).ignoreIfNull().inMinMaxRange(1, 1); + baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).ignoreIfNull().inMinMaxRange(1, 4); + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); + } + } } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformService.java index 6ca0889aa..5f9980947 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformService.java @@ -1,6 +1,7 @@ package org.mifosng.platform.loan.service; import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand; +import org.mifosng.platform.api.commands.LoanChargeCommand; import org.mifosng.platform.api.commands.LoanStateTransitionCommand; import org.mifosng.platform.api.commands.LoanTransactionCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand; @@ -45,4 +46,13 @@ public interface LoanWritePlatformService { @PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')") EntityIdentifier waiveLoanAmount(LoanTransactionCommand command); + + @PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')") + EntityIdentifier addLoanCharge(LoanChargeCommand command); + + @PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')") + EntityIdentifier updateLoanCharge(LoanChargeCommand command); + + @PreAuthorize(value = "hasRole('ORGANISATION_ADMINISTRATION_SUPER_USER_ROLE')") + EntityIdentifier deleteLoanCharge(final Long loanId, final Long loanChargeId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java index b7c9649ff..2a6025418 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -18,12 +18,17 @@ import org.mifosng.platform.api.commands.LoanTransactionCommand; import org.mifosng.platform.api.commands.UndoStateTransitionCommand; import org.mifosng.platform.api.data.EntityIdentifier; import org.mifosng.platform.api.data.LoanSchedulePeriodData; +import org.mifosng.platform.charge.domain.Charge; +import org.mifosng.platform.charge.domain.ChargeRepository; import org.mifosng.platform.client.domain.Client; import org.mifosng.platform.client.domain.ClientRepository; import org.mifosng.platform.client.domain.Note; import org.mifosng.platform.client.domain.NoteRepository; import org.mifosng.platform.currency.domain.Money; +import org.mifosng.platform.exceptions.ChargeIsNotActiveException; +import org.mifosng.platform.exceptions.ChargeNotFoundException; import org.mifosng.platform.exceptions.ClientNotFoundException; +import org.mifosng.platform.exceptions.InvalidCurrencyException; import org.mifosng.platform.exceptions.LoanNotFoundException; import org.mifosng.platform.exceptions.LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted; import org.mifosng.platform.exceptions.LoanProductNotFoundException; @@ -33,6 +38,7 @@ import org.mifosng.platform.fund.domain.Fund; import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine; import org.mifosng.platform.loan.domain.Loan; import org.mifosng.platform.loan.domain.LoanCharge; +import org.mifosng.platform.loan.domain.LoanChargeRepository; import org.mifosng.platform.loan.domain.LoanLifecycleStateMachine; import org.mifosng.platform.loan.domain.LoanProduct; import org.mifosng.platform.loan.domain.LoanProductRepository; @@ -62,12 +68,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final LoanAssembler loanAssembler; private final ClientRepository clientRepository; private final LoanProductRepository loanProductRepository; + private final ChargeRepository chargeRepository; + private final LoanChargeRepository loanChargeRepository; @Autowired public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanAssembler loanAssembler, final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, final NoteRepository noteRepository, final CalculationPlatformService calculationPlatformService, - final ClientRepository clientRepository, final LoanProductRepository loanProductRepository) { + final ClientRepository clientRepository, final LoanProductRepository loanProductRepository, + final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository) { this.context = context; this.loanAssembler = loanAssembler; this.loanRepository = loanRepository; @@ -76,6 +85,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.calculationPlatformService = calculationPlatformService; this.clientRepository = clientRepository; this.loanProductRepository = loanProductRepository; + this.chargeRepository = chargeRepository; + this.loanChargeRepository = loanChargeRepository; } private boolean isBeforeToday(final LocalDate date) { @@ -533,4 +544,89 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return new EntityIdentifier(loan.getId()); } + + @Override + public EntityIdentifier addLoanCharge(LoanChargeCommand command) { + this.context.authenticatedUser(); + + LoanChargeCommandValidator validator = new LoanChargeCommandValidator(command); + validator.validateForCreate(); + + Loan loan = this.loanRepository.findOne(command.getLoanId()); + if (loan == null) { + throw new LoanNotFoundException(command.getLoanId()); + } + + Charge chargeDefinition = this.chargeRepository.findOne(command.getChargeId()); + if (chargeDefinition == null) { + throw new ChargeNotFoundException(command.getChargeId()); + } + + LoanCharge loanCharge = LoanCharge.createNew(loan, chargeDefinition, command); + + if (!chargeDefinition.isActive()) { + throw new ChargeIsNotActiveException(chargeDefinition.getId()); + } + + if (!loan.getCurrency().getCode().equals(chargeDefinition.getCurrencyCode())){ + String errorMessage = "Charge and Loan must have the same currency."; + throw new InvalidCurrencyException("charge", "attach.to.loan", errorMessage); + } + + this.loanChargeRepository.saveAndFlush(loanCharge); + + loan.getCharges().add(loanCharge); + loan.updateTotalChargesDueAtDisbursement(); + + this.loanRepository.saveAndFlush(loan); + + return new EntityIdentifier(loanCharge.getId()); + } + + @Override + public EntityIdentifier updateLoanCharge(LoanChargeCommand command) { + + this.context.authenticatedUser(); + + LoanChargeCommandValidator validator = new LoanChargeCommandValidator(command); + validator.validateForUpdate(); + + Loan loan = this.loanRepository.findOne(command.getLoanId()); + if (loan == null) { + throw new LoanNotFoundException(command.getLoanId()); + } + + for (LoanCharge loanCharge : loan.getCharges()){ + if (loanCharge.getId().equals(command.getId())){ + loanCharge.update(command); + loan.updateTotalChargesDueAtDisbursement(); + this.loanRepository.saveAndFlush(loan); + return new EntityIdentifier(loanCharge.getId()); + } + } + + throw new ChargeNotFoundException(command.getId()); + } + + @Override + public EntityIdentifier deleteLoanCharge(Long loanId, Long loanChargeId) { + + this.context.authenticatedUser(); + + Loan loan = this.loanRepository.findOne(loanId); + if (loan == null) { + throw new LoanNotFoundException(loanId); + } + + for (LoanCharge loanCharge : loan.getCharges()){ + if (loanCharge.getId().equals(loanChargeId)){ + loan.getCharges().remove(loanCharge); + loan.updateTotalChargesDueAtDisbursement(); + this.loanRepository.saveAndFlush(loan); + return new EntityIdentifier(loanCharge.getId()); + } + } + + throw new ChargeNotFoundException(loanChargeId); + } } \ No newline at end of file