From 22fee03e0ae7097cece1a0b8c471f85b48bfa3a2 Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Mon, 26 Aug 2013 23:25:33 +0530 Subject: [PATCH] adding an api workflow for transfers --- .../V106__more_permissions_for_transfers.sql | 30 +++++ .../common/AccountingConstants.java | 12 +- ...WritePlatformServiceJpaRepositoryImpl.java | 78 ++++++------ .../CashBasedAccountingProcessorForLoan.java | 39 ++++++ .../LoanProductToGLAccountMappingHelper.java | 5 + ...AccountMappingReadPlatformServiceImpl.java | 4 + ...ccountMappingWritePlatformServiceImpl.java | 6 + .../commands/domain/CommandWrapper.java | 16 ++- .../service/CommandWrapperBuilder.java | 39 +++++- .../SynchronousCommandProcessingService.java | 10 +- .../client/api/ClientsApiResource.java | 15 ++- .../portfolio/client/domain/Client.java | 8 ++ .../client/domain/ClientEnumerations.java | 10 ++ .../portfolio/client/domain/ClientStatus.java | 20 ++++ ...tNotAwaitingTransferApprovalException.java | 12 ++ .../portfolio/group/domain/Group.java | 5 + .../group/domain/GroupingTypeStatus.java | 14 +++ .../data/LoanTransactionEnumData.java | 24 ++++ .../portfolio/loanaccount/domain/Loan.java | 14 ++- .../loanaccount/domain/LoanRepository.java | 2 +- .../loanaccount/domain/LoanStatus.java | 20 ++++ .../loanaccount/domain/LoanSummary.java | 26 ++++ .../loanaccount/domain/LoanTransaction.java | 14 +++ .../domain/LoanTransactionType.java | 24 +++- .../service/LoanWritePlatformService.java | 18 ++- ...WritePlatformServiceJpaRepositoryImpl.java | 71 +++++++++++ .../LoanProductDataValidator.java | 42 ++++--- .../loanproduct/service/LoanEnumerations.java | 30 ++++- .../transfer/api/TransferApiConstants.java | 4 +- .../transfer/data/TransfersDataValidator.java | 4 +- ...> AcceptClientTransferCommandHandler.java} | 6 +- .../ProposeClientTransferCommandHandler.java | 32 +++++ .../RejectClientTransferCommandHandler.java | 32 +++++ .../WithdrawClientTransferCommandHandler.java | 32 +++++ .../transfer/service/TransferEventType.java | 29 +++++ .../service/TransferWritePlatformService.java | 8 +- ...WritePlatformServiceJpaRepositoryImpl.java | 112 ++++++++++++++++-- 37 files changed, 764 insertions(+), 103 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V106__more_permissions_for_transfers.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientNotAwaitingTransferApprovalException.java rename mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/{TransferClientBetweenBranchesCommandHandler.java => AcceptClientTransferCommandHandler.java} (76%) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/ProposeClientTransferCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/RejectClientTransferCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/WithdrawClientTransferCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferEventType.java diff --git a/mifosng-db/migrations/core_db/V106__more_permissions_for_transfers.sql b/mifosng-db/migrations/core_db/V106__more_permissions_for_transfers.sql new file mode 100644 index 000000000..3b99dea4e --- /dev/null +++ b/mifosng-db/migrations/core_db/V106__more_permissions_for_transfers.sql @@ -0,0 +1,30 @@ +DELETE FROM `m_permission` WHERE `code`="TRANSFER_CLIENT"; +DELETE FROM `m_permission` WHERE `code`="TRANSFER_CLIENT_CHECKER"; + +/**Permissions for proposing a transfer**/ +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'PROPOSETRANSFER_CLIENT', 'CLIENT', 'PROPOSETRANSFER', 0); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'PROPOSETRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSETRANSFER', 0); + +/**Permissions for accepting a transfer**/ +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'ACCEPTTRANSFER_CLIENT', 'CLIENT', 'ACCEPTTRANSFER', 0); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'ACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'ACCEPTTRANSFER', 0); + +/**Permissions for rejecting a transfer**/ +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'REJECTTRANSFER_CLIENT', 'CLIENT', 'REJECTTRANSFER', 0); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'REJECTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'REJECTTRANSFER', 0); + +/**Permissions for withdrawing a transfer proposal**/ +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'WITHDRAWTRANSFER_CLIENT', 'CLIENT', 'WITHDRAWTRANSFER', 0); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'WITHDRAWTRANSFER_CLIENT_CHECKER', 'CLIENT', 'WITHDRAWTRANSFER', 0); \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java index 35de2586a..c6afeccab 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/common/AccountingConstants.java @@ -12,7 +12,8 @@ public class AccountingConstants { /*** Accounting placeholders for cash based accounting for loan products ***/ public static enum CASH_ACCOUNTS_FOR_LOAN { - FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6); + FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), TRANSFERS_SUSPENSE( + 10); private final Integer value; @@ -45,7 +46,7 @@ public class AccountingConstants { /*** Accounting placeholders for accrual based accounting for loan products ***/ public static enum ACCRUAL_ACCOUNTS_FOR_LOAN { FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF(6), INTEREST_RECEIVABLE( - 7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9); + 7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), TRANSFERS_SUSPENSE(10); private final Integer value; @@ -84,9 +85,10 @@ public class AccountingConstants { FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS("interestOnLoanAccountId"), INCOME_FROM_FEES( "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), INTEREST_RECEIVABLE( "receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE( - "receivablePenaltyAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE( - "paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( - "penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId"); + "receivablePenaltyAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( + "paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING( + "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID( + "incomeAccountId"); private final String value; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java index 6b975c89b..fddfb937c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/glaccount/service/GLAccountWritePlatformServiceJpaRepositoryImpl.java @@ -76,11 +76,11 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount final Long tagId = command.longValueOfParameterNamed(GLAccountJsonInputParams.TAGID.getValue()); final Long type = command.longValueOfParameterNamed(GLAccountJsonInputParams.TYPE.getValue()); GLAccountType accountType = GLAccountType.fromInt(type.intValue()); - - if(tagId != null) { - glAccountTagType = retrieveTagId(tagId, accountType); - } - + + if (tagId != null) { + glAccountTagType = retrieveTagId(tagId, accountType); + } + final GLAccount glAccount = GLAccount.fromJson(parentGLAccount, command, glAccountTagType); this.glAccountRepository.saveAndFlush(glAccount); @@ -104,9 +104,7 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount accountCommand.validateForUpdate(); final Long parentId = command.longValueOfParameterNamed(GLAccountJsonInputParams.PARENT_ID.getValue()); - if (glAccountId.equals(parentId)) { - throw new InvalidParentGLAccountHeadException(glAccountId,parentId); - } + if (glAccountId.equals(parentId)) { throw new InvalidParentGLAccountHeadException(glAccountId, parentId); } // is the glAccount valid final GLAccount glAccount = this.glAccountRepository.findOne(glAccountId); if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); } @@ -118,15 +116,15 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount GLAccount parentAccount = validateParentGLAccount(parentId); glAccount.updateParentAccount(parentAccount); } - + if (changesOnly.containsKey(GLAccountJsonInputParams.TAGID.getValue())) { - final Long tagIdLongValue = command.longValueOfParameterNamed(GLAccountJsonInputParams.TAGID.getValue()); - final GLAccountType accountType = GLAccountType.fromInt(glAccount.getType()); - CodeValue tagID = null; - if (tagIdLongValue != null) { - tagID = retrieveTagId(tagIdLongValue, accountType); - } - glAccount.updateTagId(tagID); + final Long tagIdLongValue = command.longValueOfParameterNamed(GLAccountJsonInputParams.TAGID.getValue()); + final GLAccountType accountType = GLAccountType.fromInt(glAccount.getType()); + CodeValue tagID = null; + if (tagIdLongValue != null) { + tagID = retrieveTagId(tagIdLongValue, accountType); + } + glAccount.updateTagId(tagID); } /** @@ -180,11 +178,11 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount */ private GLAccount validateParentGLAccount(final Long parentAccountId) { GLAccount parentGLAccount = null; - if (parentAccountId != null){ - parentGLAccount = this.glAccountRepository.findOne(parentAccountId); - if (parentGLAccount == null) { throw new GLAccountNotFoundException(parentAccountId); } - // ensure parent is not a detail account - if (parentGLAccount.isDetailAccount()) { throw new GLAccountInvalidParentException(parentAccountId); } + if (parentAccountId != null) { + parentGLAccount = this.glAccountRepository.findOne(parentAccountId); + if (parentGLAccount == null) { throw new GLAccountNotFoundException(parentAccountId); } + // ensure parent is not a detail account + if (parentGLAccount.isDetailAccount()) { throw new GLAccountInvalidParentException(parentAccountId); } } return parentGLAccount; } @@ -204,26 +202,26 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount throw new PlatformDataIntegrityException("error.msg.glAccount.unknown.data.integrity.issue", "Unknown data integrity issue with resource GL Account: " + realCause.getMessage()); } - + private CodeValue retrieveTagId(Long tagId, GLAccountType accountType) { - CodeValue glAccountTagType = null; - if (accountType.isAssetType()) { - glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( - AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME, tagId); - } else if (accountType.isLiabilityType()) { - glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( - AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME, tagId); - } else if (accountType.isEquityType()) { - glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( - AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME, tagId); - } else if (accountType.isIncomeType()) { - glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( - AccountingConstants.INCOME_TAG_OPTION_CODE_NAME, tagId); - } else if (accountType.isExpenseType()) { - glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( - AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME, tagId); - } - return glAccountTagType; + CodeValue glAccountTagType = null; + if (accountType.isAssetType()) { + glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( + AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME, tagId); + } else if (accountType.isLiabilityType()) { + glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( + AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME, tagId); + } else if (accountType.isEquityType()) { + glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( + AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME, tagId); + } else if (accountType.isIncomeType()) { + glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( + AccountingConstants.INCOME_TAG_OPTION_CODE_NAME, tagId); + } else if (accountType.isExpenseType()) { + glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( + AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME, tagId); + } + return glAccountTagType; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java index 0eca865cd..e2f69f4df 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java @@ -63,6 +63,9 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, false); + } else if (loanTransactionDTO.getTransactionType().isInitiateTransfer() + || loanTransactionDTO.getTransactionType().isApproveTransfer()) { + createJournalEntriesForTransfers(loanDTO, loanTransactionDTO, office); } } } @@ -151,4 +154,40 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF helper.createDebitJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); } + + /** + * Credit loan Portfolio and Debit Suspense Account for a Transfer + * Initiation. A Transfer acceptance would be treated the opposite i.e Debit + * Loan Portfolio and Credit Suspense Account
+ * + * All debits are turned into credits and vice versa in case of Transfer + * Initiation disbursals + * + * + * @param loanDTO + * @param loanTransactionDTO + * @param office + */ + private void createJournalEntriesForTransfers(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, final Office office) { + // loan properties + final Long loanProductId = loanDTO.getLoanProductId(); + final Long loanId = loanDTO.getLoanId(); + + // transaction properties + final String transactionId = loanTransactionDTO.getTransactionId(); + final Date transactionDate = loanTransactionDTO.getTransactionDate(); + final BigDecimal principalAmount = loanTransactionDTO.getPrincipal(); + final boolean isReversal = loanTransactionDTO.isReversed(); + // final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); + + if (loanTransactionDTO.getTransactionType().isInitiateTransfer()) { + helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE, + CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, null, loanId, transactionId, transactionDate, principalAmount, + isReversal); + } else if (loanTransactionDTO.getTransactionType().isApproveTransfer()) { + helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, + CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE, loanProductId, null, loanId, transactionId, transactionDate, + principalAmount, isReversal); + } + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java index 1dcb5ca36..e3b12c28f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java @@ -167,6 +167,8 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue(), CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.toString(), changes); // income mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, @@ -188,6 +190,9 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.toString(), changes); + mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.toString(), + changes); mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(), changes); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index c4bf1d4aa..392538403 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -102,6 +102,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), glAccountId); } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) { accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), glAccountId); + } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE)) { + accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId); } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) { accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), glAccountId); } @@ -124,6 +126,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), glAccountId); } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) { accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), glAccountId); + } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE)) { + accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), glAccountId); } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) { accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), glAccountId); } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index 2feeb8706..c95e4133a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -63,6 +63,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); + loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), loanProductId, + CASH_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue()); // income loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, @@ -93,6 +96,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); + loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, + LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.TRANSFERS_SUSPENSE.getValue()); loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 90ff1c732..effa71a75 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -564,8 +564,20 @@ public class CommandWrapper { return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT"); } - public boolean isClientTransfer() { - return this.actionName.equalsIgnoreCase("TRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); + public boolean isProposeClientTransfer() { + return this.actionName.equalsIgnoreCase("PROPOSETRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); + } + + public boolean isWithdrawClientTransfer() { + return this.actionName.equalsIgnoreCase("WITHDRAWTRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); + } + + public boolean isAcceptClientTransfer() { + return this.actionName.equalsIgnoreCase("ACCEPTTRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); + } + + public boolean isRejectClientTransfer() { + return this.actionName.equalsIgnoreCase("REJECTTRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); } public boolean isProductMixResource() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index 49d7838ad..a2ea424f6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -351,13 +351,40 @@ public class CommandWrapperBuilder { this.href = "/clients/" + clientId + "?command=close&template=true"; return this; } - - public CommandWrapperBuilder transferClient(final Long clientId) { - this.actionName = "TRANSFER"; + + public CommandWrapperBuilder proposeClientTransfer(final Long clientId) { + this.actionName = "PROPOSETRANSFER"; this.entityName = "CLIENT"; this.entityId = clientId; this.clientId = clientId; - this.href = "/clientId/" + clientId + "?command=transfer"; + this.href = "/clientId/" + clientId + "?command=proposeTransfer"; + return this; + } + + public CommandWrapperBuilder withdrawClientTransferRequest(final Long clientId) { + this.actionName = "PROPOSETRANSFER"; + this.entityName = "CLIENT"; + this.entityId = clientId; + this.clientId = clientId; + this.href = "/clientId/" + clientId + "?command=withdrawTransfer"; + return this; + } + + public CommandWrapperBuilder acceptClientTransfer(final Long clientId) { + this.actionName = "ACCEPTTRANSFER"; + this.entityName = "CLIENT"; + this.entityId = clientId; + this.clientId = clientId; + this.href = "/clientId/" + clientId + "?command=acceptTransfer"; + return this; + } + + public CommandWrapperBuilder rejectClientTransfer(final Long clientId) { + this.actionName = "REJECTTRANSFER"; + this.entityName = "CLIENT"; + this.entityId = clientId; + this.clientId = clientId; + this.href = "/clientId/" + clientId + "?command=rejectTransfer"; return this; } @@ -1009,7 +1036,7 @@ public class CommandWrapperBuilder { this.href = "/groups/" + groupId + "?command=disassociateClients"; return this; } - + public CommandWrapperBuilder transferClientsBetweenGroups(final Long sourceGroupId) { this.actionName = "TRANSFERCLIENTS"; this.entityName = "GROUP"; @@ -1253,7 +1280,7 @@ public class CommandWrapperBuilder { this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId; return this; } - + public CommandWrapperBuilder saveOrUpdateAttendance(final Long entityId, final String supportedEntityType, final Long supportedEntityId) { this.actionName = "SAVEORUPDATEATTENDANCE"; this.entityName = "MEETING"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index b8822b31c..2dae7c32a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -170,8 +170,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer handler = this.applicationContext.getBean("assignClientStaffCommandHandler", NewCommandSourceHandler.class); } else if (wrapper.isClientClose()) { handler = this.applicationContext.getBean("closeClientCommandHandler", NewCommandSourceHandler.class); - } else if (wrapper.isClientTransfer()) { - handler = this.applicationContext.getBean("transferClientBetweenBranchesCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isProposeClientTransfer()) { + handler = this.applicationContext.getBean("proposeClientTransferCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isWithdrawClientTransfer()) { + handler = this.applicationContext.getBean("withdrawClientTransferCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isAcceptClientTransfer()) { + handler = this.applicationContext.getBean("acceptClientTransferCommandHandler", NewCommandSourceHandler.class); + } else if (wrapper.isRejectClientTransfer()) { + handler = this.applicationContext.getBean("rejectClientTransferCommandHandler", NewCommandSourceHandler.class); } else { throw new UnsupportedCommandException(wrapper.commandName()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java index 4fc537a27..beaa7792b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java @@ -207,13 +207,22 @@ public class ClientsApiResource { } else if (is(commandParam, "close")) { commandRequest = builder.closeClient(clientId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - }else if (is(commandParam, "transfer")) { - commandRequest = builder.transferClient(clientId).build(); + } else if (is(commandParam, "proposeTransfer")) { + commandRequest = builder.proposeClientTransfer(clientId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "withdrawTransfer")) { + commandRequest = builder.withdrawClientTransferRequest(clientId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "acceptTransfer")) { + commandRequest = builder.acceptClientTransfer(clientId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "rejectTransfer")) { + commandRequest = builder.rejectClientTransfer(clientId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", - "unassignStaff", "assignStaff" }); } + "unassignStaff", "assignStaff", "close", "proposeTransfer", "withdrawTransfer", "acceptTransfer", "rejectTransfer" }); } return this.toApiJsonSerializer.serialize(result); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java index aaf9fca4b..d033c0dca 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java @@ -252,6 +252,10 @@ public final class Client extends AbstractPersistable { public boolean isActive() { return ClientStatus.fromInt(this.status).isActive(); } + + public boolean isTransferInProgress () { + return ClientStatus.fromInt(this.status).isTransferInProgress(); + } public boolean isNotPending() { return !isPending(); @@ -472,6 +476,10 @@ public final class Client extends AbstractPersistable { public Integer getStatus() { return this.status; } + + public void setStatus(Integer status) { + this.status = status; + } public boolean isActivatedAfter(final LocalDate submittedOn) { return getActivationLocalDate().isAfter(submittedOn); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientEnumerations.java index 40aa1fb38..bbffdfcfa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientEnumerations.java @@ -29,6 +29,16 @@ public class ClientEnumerations { case CLOSED: optionData = new EnumOptionData(ClientStatus.CLOSED.getValue().longValue(), ClientStatus.CLOSED.getCode(), "Closed"); break; + case TRANSFER_IN_PROGRESS: + optionData = new EnumOptionData(ClientStatus.TRANSFER_IN_PROGRESS.getValue().longValue(), + ClientStatus.TRANSFER_IN_PROGRESS.getCode(), "Transfer in progress"); + break; + case TRANSFER_ON_HOLD: + optionData = new EnumOptionData(ClientStatus.TRANSFER_ON_HOLD.getValue().longValue(), + ClientStatus.TRANSFER_ON_HOLD.getCode(), "Transfer on hold"); + break; + default: + break; } return optionData; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientStatus.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientStatus.java index 47088c1a5..327db48b7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientStatus.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientStatus.java @@ -13,6 +13,8 @@ public enum ClientStatus { INVALID(0, "clientStatusType.invalid"), // PENDING(100, "clientStatusType.pending"), // ACTIVE(300, "clientStatusType.active"), // + TRANSFER_IN_PROGRESS(303, "clientStatusType.transfer.in.progress"), // + TRANSFER_ON_HOLD(304, "clientStatusType.transfer.on.hold"), // CLOSED(600, "clientStatusType.closed"); private final Integer value; @@ -27,6 +29,12 @@ public enum ClientStatus { break; case 300: enumeration = ClientStatus.ACTIVE; + break; + case 303: + enumeration = ClientStatus.TRANSFER_IN_PROGRESS; + break; + case 304: + enumeration = ClientStatus.TRANSFER_ON_HOLD; break; case 600: enumeration = ClientStatus.CLOSED; @@ -63,4 +71,16 @@ public enum ClientStatus { public boolean isClosed() { return this.value.equals(ClientStatus.CLOSED.getValue()); } + + public boolean isTransferInProgress() { + return this.value.equals(ClientStatus.TRANSFER_IN_PROGRESS.getValue()); + } + + public boolean isTransferOnHold() { + return this.value.equals(ClientStatus.TRANSFER_ON_HOLD.getValue()); + } + + public boolean isUnderTransfer() { + return isTransferInProgress() || isTransferOnHold(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientNotAwaitingTransferApprovalException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientNotAwaitingTransferApprovalException.java new file mode 100644 index 000000000..998ad1eb6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientNotAwaitingTransferApprovalException.java @@ -0,0 +1,12 @@ +package org.mifosplatform.portfolio.client.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class ClientNotAwaitingTransferApprovalException extends AbstractPlatformDomainRuleException { + + public ClientNotAwaitingTransferApprovalException(final Long clientId) { + super("error.msg.client.not.awaiting.transfer.approval.exception", + "The Client with id `" + clientId + "` is not awaiting transfer", clientId); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java index 65a9915de..26e70aafe 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java @@ -38,6 +38,7 @@ import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.portfolio.client.domain.Client; +import org.mifosplatform.portfolio.client.domain.ClientStatus; import org.mifosplatform.portfolio.group.api.GroupingTypesApiConstants; import org.mifosplatform.portfolio.group.exception.ClientExistInGroupException; import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException; @@ -385,6 +386,10 @@ public final class Group extends AbstractPersistable { return this.groupLevel.isCenter(); } + public boolean isTransferInProgress () { + return GroupingTypeStatus.fromInt(this.status).isTransferInProgress(); + } + public boolean isChildClient(final Long clientId){ if (clientId != null && this.clientMembers != null && !this.clientMembers.isEmpty()) { for (Client client : this.clientMembers) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupingTypeStatus.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupingTypeStatus.java index e0add459d..a52b1d1be 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupingTypeStatus.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupingTypeStatus.java @@ -13,6 +13,8 @@ public enum GroupingTypeStatus { INVALID(0, "groupingStatusType.invalid"), // PENDING(100, "groupingStatusType.pending"), // ACTIVE(300, "groupingStatusType.active"), // + TRANSFER_IN_PROGRESS(303, "clientStatusType.transfer.in.progress"), // + TRANSFER_ON_HOLD(304, "clientStatusType.transfer.on.hold"), // CLOSED(600, "groupingStatusType.closed"); private final Integer value; @@ -63,4 +65,16 @@ public enum GroupingTypeStatus { public boolean isClosed() { return this.value.equals(GroupingTypeStatus.CLOSED.getValue()); } + + public boolean isTransferInProgress() { + return this.isTransferInProgress(); + } + + public boolean isTransferOnHold() { + return this.isTransferOnHold(); + } + + public boolean isUnderTransfer() { + return isTransferInProgress() || isTransferOnHold(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java index b1cf8e3e2..dfec6baa5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTransactionEnumData.java @@ -25,6 +25,10 @@ public class LoanTransactionEnumData { private final boolean applyCharges; private final boolean writeOff; private final boolean recoveryRepayment; + private final boolean initiateTransfer; + private final boolean approveTransfer; + private final boolean withdrawTransfer; + private final boolean rejectTransfer; public LoanTransactionEnumData(final Long id, final String code, final String value) { this.id = id; @@ -40,6 +44,10 @@ public class LoanTransactionEnumData { this.applyInterest = Long.valueOf(11).equals(this.id); this.writeOff = Long.valueOf(6).equals(this.id); this.recoveryRepayment = Long.valueOf(8).equals(this.id); + this.initiateTransfer = Long.valueOf(12).equals(this.id); + this.approveTransfer = Long.valueOf(13).equals(this.id); + this.withdrawTransfer = Long.valueOf(14).equals(this.id); + this.rejectTransfer = Long.valueOf(15).equals(this.id); } public Long id() { @@ -93,4 +101,20 @@ public class LoanTransactionEnumData { return this.applyInterest; } + public boolean isInitiateTransfer() { + return this.initiateTransfer; + } + + public boolean isApproveTransfer() { + return this.approveTransfer; + } + + public boolean isWithdrawTransfer() { + return this.withdrawTransfer; + } + + public boolean isRejectTransfer() { + return this.rejectTransfer; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index f1b3b9b04..3b57cb70e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -1281,7 +1281,7 @@ public class Loan extends AbstractPersistable { return actualChanges; } - private Collection findExistingTransactionIds() { + public Collection findExistingTransactionIds() { final Collection ids = new ArrayList(); @@ -1292,7 +1292,7 @@ public class Loan extends AbstractPersistable { return ids; } - private Collection findExistingReversedTransactionIds() { + public Collection findExistingReversedTransactionIds() { final Collection ids = new ArrayList(); @@ -2011,7 +2011,7 @@ public class Loan extends AbstractPersistable { return hasDisbursementTransaction(); } - private boolean isClosed() { + public boolean isClosed() { return status().isClosed() || isCancelled(); } @@ -2578,6 +2578,14 @@ public class Loan extends AbstractPersistable { public Integer getTermPeriodFrequencyType() { return this.termPeriodFrequencyType; } + + public List getLoanTransactions() { + return this.loanTransactions; + } + + public void setLoanStatus(Integer loanStatus) { + this.loanStatus = loanStatus; + } public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final List holidays, final boolean allowTransactionsOnNonWorkingDay) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java index 07854f45b..e868efaee 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java @@ -52,7 +52,7 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat public static final String FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP = "Select loan.loanProduct.id from Loan loan where " + "loan.group.id = :groupId and loan.loanStatus = :loanStatus and loan.client.id is NULL group by loan.loanProduct.id"; - public static final String DOES_CLIENT_HAVE_ACTIVE_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.client.id = :clientId and loan.loanStatus in (100,200,300)"; + public static final String DOES_CLIENT_HAVE_ACTIVE_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.client.id = :clientId and loan.loanStatus in (100,200,300,303,304)"; @Query(FIND_GROUP_LOANS_DISBURSED_AFTER) List getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java index 958a551f2..d1d8bed8d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java @@ -14,6 +14,8 @@ public enum LoanStatus { SUBMITTED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), // APPROVED(200, "loanStatusType.approved"), // ACTIVE(300, "loanStatusType.active"), // + TRANSFER_IN_PROGRESS(303, "loanStatusType.transfer.in.progress"), // + TRANSFER_ON_HOLD(304, "loanStatusType.transfer.on.hold"), // WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), // REJECTED(500, "loanStatusType.rejected"), // CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), // @@ -37,6 +39,12 @@ public enum LoanStatus { case 300: enumeration = LoanStatus.ACTIVE; break; + case 303: + enumeration = LoanStatus.TRANSFER_IN_PROGRESS; + break; + case 304: + enumeration = LoanStatus.TRANSFER_ON_HOLD; + break; case 400: enumeration = LoanStatus.WITHDRAWN_BY_CLIENT; break; @@ -115,4 +123,16 @@ public enum LoanStatus { public boolean isActiveOrAwaitingApprovalOrDisbursal() { return this.isApproved() || this.isSubmittedAndPendingApproval() || this.isActive(); } + + public boolean isTransferInProgress() { + return this.isTransferInProgress(); + } + + public boolean isTransferOnHold() { + return this.isTransferOnHold(); + } + + public boolean isUnderTransfer() { + return isTransferInProgress() || isTransferOnHold(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java index 98ffcc58a..72812237c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java @@ -137,6 +137,32 @@ public final class LoanSummary { return this.totalInterestCharged; } + + public BigDecimal getTotalPrincipalOutstanding() { + return this.totalPrincipalOutstanding; + } + + + public BigDecimal getTotalInterestOutstanding() { + return this.totalInterestOutstanding; + } + + + public BigDecimal getTotalFeeChargesOutstanding() { + return this.totalFeeChargesOutstanding; + } + + + public BigDecimal getTotalPenaltyChargesOutstanding() { + return this.totalPenaltyChargesOutstanding; + } + + + public BigDecimal getTotalOutstanding() { + return this.totalOutstanding; + } + + /** * All fields but totalFeeChargesDueAtDisbursement should be * reset. diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 32302df4f..b83f622fe 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -123,6 +123,20 @@ public final class LoanTransaction extends AbstractPersistable { return new LoanTransaction(loan, office, LoanTransactionType.APPLY_INTEREST, amount.getAmount(), interestAppliedDate, null); } + public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate) { + return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(), + loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() + .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() + .getTotalPenaltyChargesOutstanding(), false, null); + } + + public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate) { + return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(), + loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() + .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() + .getTotalPenaltyChargesOutstanding(), false, null); + } + public static LoanTransaction copyTransactionProperties(LoanTransaction loanTransaction) { return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf, loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java index 5d2f6f2d0..f691ccda2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransactionType.java @@ -22,7 +22,17 @@ public enum LoanTransactionType { RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), // WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), // APPLY_CHARGES(10, "loanTransactionType.applyCharges"), // - APPLY_INTEREST(11, "loanTransactionType.applyInterest"); + APPLY_INTEREST(11, "loanTransactionType.applyInterest"), + + /*** + * A Loan Transfer involves two steps, first a "initiate" Loan transfer + * transaction done by the Source branch followed by a "complete" loan + * transaction initiated by the destination branch + **/ + INITIATE_TRANSFER(12, "loanTransactionType.initiateTransfer"), // + APPROVE_TRANSFER(13, "loanTransactionType.approveTransfer"), // + WITHDRAW_TRANSFER(14, "loanTransactionType.withdrawTransfer"), // + REJECT_TRANSFER(15, "loanTransactionType.rejectTransfer"); private final Integer value; private final String code; @@ -79,6 +89,18 @@ public enum LoanTransactionType { case 11: loanTransactionType = LoanTransactionType.APPLY_INTEREST; break; + case 12: + loanTransactionType = LoanTransactionType.INITIATE_TRANSFER; + break; + case 13: + loanTransactionType = LoanTransactionType.APPROVE_TRANSFER; + break; + case 14: + loanTransactionType = LoanTransactionType.WITHDRAW_TRANSFER; + break; + case 15: + loanTransactionType = LoanTransactionType.REJECT_TRANSFER; + break; default: loanTransactionType = LoanTransactionType.INVALID; break; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java index e410610ed..c43b665de 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -8,23 +8,25 @@ package org.mifosplatform.portfolio.loanaccount.service; import java.util.Collection; import java.util.Map; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; public interface LoanWritePlatformService { CommandProcessingResult disburseLoan(Long loanId, JsonCommand command); - + Map bulkLoanDisbursal(JsonCommand command, CollectionSheetBulkDisbursalCommand bulkDisbursalCommand); CommandProcessingResult undoLoanDisbursal(Long loanId, JsonCommand command); CommandProcessingResult makeLoanRepayment(Long loanId, JsonCommand command); - + Map makeLoanBulkRepayment(CollectionSheetBulkRepaymentCommand bulkRepaymentCommand); CommandProcessingResult adjustLoanTransaction(Long loanId, Long transactionId, JsonCommand command); @@ -50,8 +52,16 @@ public interface LoanWritePlatformService { CommandProcessingResult bulkLoanReassignment(JsonCommand command); CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command); - + void applyMeetingDateChanges(Calendar calendar, Collection loanCalendarInstances); - + void applyHolidaysToLoans(); + + LoanTransaction initiateLoanTransfer(Long accountId, LocalDate TransferDate); + + LoanTransaction acceptLoanTransfer(Long accountId, LocalDate TransferDate); + + LoanTransaction withdrawLoanTransfer(Long accountId, LocalDate TransferDate); + + LoanTransaction rejectLoanTransfer(Long accountId, LocalDate TransferDate); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 79791fc3e..3035d3aaf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -51,6 +51,7 @@ import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedExce import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; +import org.mifosplatform.portfolio.client.exception.ClientNotAwaitingTransferApprovalException; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand; @@ -892,6 +893,64 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return loanCharge; } + @Transactional + @Override + public LoanTransaction initiateLoanTransfer(final Long accountId, final LocalDate TransferDate) { + + final Loan loan = this.loanAssembler.assembleFrom(accountId); + checkClientOrGroupActive(loan); + + final List existingTransactionIds = new ArrayList(loan.findExistingTransactionIds()); + final List existingReversedTransactionIds = new ArrayList(loan.findExistingReversedTransactionIds()); + + final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, TransferDate); + loan.getLoanTransactions().add(newTransferTransaction); + loan.setLoanStatus(LoanStatus.TRANSFER_IN_PROGRESS.getValue()); + + this.loanTransactionRepository.save(newTransferTransaction); + this.loanRepository.save(loan); + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + + return newTransferTransaction; + } + + @Transactional + @Override + public LoanTransaction acceptLoanTransfer(final Long accountId, final LocalDate TransferDate) { + + final Loan loan = this.loanAssembler.assembleFrom(accountId); + validateClientOrGroupAwaitingTransferAcceptance(loan); + + final List existingTransactionIds = new ArrayList(loan.findExistingTransactionIds()); + final List existingReversedTransactionIds = new ArrayList(loan.findExistingReversedTransactionIds()); + + final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(loan.getOffice(), loan, TransferDate); + loan.getLoanTransactions().add(newTransferAcceptanceTransaction); + loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); + + this.loanTransactionRepository.save(newTransferAcceptanceTransaction); + this.loanRepository.save(loan); + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + + return newTransferAcceptanceTransaction; + } + + @Transactional + @Override + public LoanTransaction withdrawLoanTransfer(Long accountId, LocalDate TransferDate) { + // TODO Auto-generated method stub + return null; + } + + @Transactional + @Override + public LoanTransaction rejectLoanTransfer(Long accountId, LocalDate TransferDate) { + // TODO Auto-generated method stub + return null; + } + @Transactional @Override public CommandProcessingResult loanReassignment(final Long loanId, final JsonCommand command) { @@ -1256,4 +1315,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); } } } + + private void validateClientOrGroupAwaitingTransferAcceptance(final Loan loan) { + final Client client = loan.client(); + if (client != null) { + if (!client.isTransferInProgress()) { throw new ClientNotAwaitingTransferApprovalException(client.getId()); } + } + final Group group = loan.group(); + if (group != null) { + if (!group.isTransferInProgress()) { throw new ClientNotAwaitingTransferApprovalException(group.getId()); } + } + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java index f195673cb..4822a87dd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -38,17 +38,17 @@ public final class LoanProductDataValidator { /** * The parameters supported for this command. */ - private final Set supportedParameters = new HashSet(Arrays.asList("locale", "dateFormat", "name", "description", "fundId", - "currencyCode", "digitsAfterDecimal","inMultiplesOf", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", "numberOfRepayments", - "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod", + private final Set supportedParameters = new HashSet(Arrays.asList("locale", "dateFormat", "name", "description", + "fundId", "currencyCode", "digitsAfterDecimal", "inMultiplesOf", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", + "numberOfRepayments", "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod", "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "graceOnPrincipalPayment", - "graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule","includeInBorrowerCycle", "startDate", "closeDate", "externalId", - LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), - LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), + "graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule", "includeInBorrowerCycle", "startDate", + "closeDate", "externalId", LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), + LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), + LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), + LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), + LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue())); @@ -81,9 +81,10 @@ public final class LoanProductDataValidator { final Long fundId = fromApiJsonHelper.extractLongNamed("fundId", element); baseDataValidator.reset().parameter("fundId").value(fundId).ignoreIfNull().integerGreaterThanZero(); } - + final Boolean includeInBorrowerCycle = fromApiJsonHelper.extractBooleanNamed("includeInBorrowerCycle", element); - baseDataValidator.reset().parameter("includeInBorrowerCycle").value(includeInBorrowerCycle).ignoreIfNull().validateForBooleanValue(); + baseDataValidator.reset().parameter("includeInBorrowerCycle").value(includeInBorrowerCycle).ignoreIfNull() + .validateForBooleanValue(); // terms final String currencyCode = fromApiJsonHelper.extractStringNamed("currencyCode", element); @@ -91,7 +92,7 @@ public final class LoanProductDataValidator { final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerNamed("digitsAfterDecimal", element, Locale.getDefault()); baseDataValidator.reset().parameter("digitsAfterDecimal").value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6); - + final Integer inMultiplesOf = fromApiJsonHelper.extractIntegerNamed("inMultiplesOf", element, Locale.getDefault()); baseDataValidator.reset().parameter("inMultiplesOf").value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater(); @@ -252,6 +253,11 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId) .notNull().integerGreaterThanZero(); + final Long transfersInSuspenseAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue()) + .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero(); + final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed( LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId) @@ -322,10 +328,11 @@ public final class LoanProductDataValidator { final Long fundId = fromApiJsonHelper.extractLongNamed("fundId", element); baseDataValidator.reset().parameter("fundId").value(fundId).ignoreIfNull().integerGreaterThanZero(); } - + if (fromApiJsonHelper.parameterExists("includeInBorrowerCycle", element)) { final Boolean includeInBorrowerCycle = fromApiJsonHelper.extractBooleanNamed("includeInBorrowerCycle", element); - baseDataValidator.reset().parameter("includeInBorrowerCycle").value(includeInBorrowerCycle).ignoreIfNull().validateForBooleanValue(); + baseDataValidator.reset().parameter("includeInBorrowerCycle").value(includeInBorrowerCycle).ignoreIfNull() + .validateForBooleanValue(); } if (fromApiJsonHelper.parameterExists("currencyCode", element)) { @@ -337,7 +344,7 @@ public final class LoanProductDataValidator { final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerNamed("digitsAfterDecimal", element, Locale.getDefault()); baseDataValidator.reset().parameter("digitsAfterDecimal").value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6); } - + if (fromApiJsonHelper.parameterExists("inMultiplesOf", element)) { final Integer inMultiplesOf = fromApiJsonHelper.extractIntegerNamed("inMultiplesOf", element, Locale.getDefault()); baseDataValidator.reset().parameter("inMultiplesOf").value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater(); @@ -478,6 +485,11 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId) .ignoreIfNull().integerGreaterThanZero(); + final Long transfersInSuspenseAccountId = fromApiJsonHelper.extractLongNamed( + LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), element); + baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue()) + .value(transfersInSuspenseAccountId).ignoreIfNull().integerGreaterThanZero(); + final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index 6ebdea8e9..0aeecb9bb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -16,7 +16,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodM import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; -public class LoanEnumerations{ +public class LoanEnumerations { public static final String LOAN_TERM_FREQUENCY_TYPE = "loanTermFrequencyType"; public static final String TERM_FREQUENCY_TYPE = "termFrequencyType"; @@ -282,6 +282,24 @@ public class LoanEnumerations{ optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_INTEREST.getValue().longValue(), LoanTransactionType.APPLY_INTEREST.getCode(), "Apply Interest"); break; + case APPROVE_TRANSFER: + optionData = new LoanTransactionEnumData(LoanTransactionType.APPROVE_TRANSFER.getValue().longValue(), + LoanTransactionType.APPROVE_TRANSFER.getCode(), "Transfer approved"); + break; + case INITIATE_TRANSFER: + optionData = new LoanTransactionEnumData(LoanTransactionType.INITIATE_TRANSFER.getValue().longValue(), + LoanTransactionType.INITIATE_TRANSFER.getCode(), "Transfer initiated"); + break; + case WITHDRAW_TRANSFER: + optionData = new LoanTransactionEnumData(LoanTransactionType.WITHDRAW_TRANSFER.getValue().longValue(), + LoanTransactionType.WITHDRAW_TRANSFER.getCode(), "Transfer Withdrawn"); + break; + case REJECT_TRANSFER: + optionData = new LoanTransactionEnumData(LoanTransactionType.REJECT_TRANSFER.getValue().longValue(), + LoanTransactionType.REJECT_TRANSFER.getCode(), "Transfer Rejected"); + break; + default: + break; } return optionData; } @@ -329,6 +347,16 @@ public class LoanEnumerations{ case OVERPAID: optionData = new LoanStatusEnumData(LoanStatus.OVERPAID.getValue().longValue(), LoanStatus.OVERPAID.getCode(), "Overpaid"); break; + case TRANSFER_IN_PROGRESS: + optionData = new LoanStatusEnumData(LoanStatus.TRANSFER_IN_PROGRESS.getValue().longValue(), + LoanStatus.TRANSFER_IN_PROGRESS.getCode(), "Transfer in progress"); + break; + case TRANSFER_ON_HOLD: + optionData = new LoanStatusEnumData(LoanStatus.TRANSFER_ON_HOLD.getValue().longValue(), + LoanStatus.TRANSFER_ON_HOLD.getCode(), "Transfer in hold"); + break; + default: + break; } return optionData; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java index 4920de273..fb0857097 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java @@ -28,7 +28,7 @@ public class TransferApiConstants { dateFormatParamName, destinationGroupIdParamName, clients, inheritDestinationGroupLoanOfficer, newStaffIdParamName, transferActiveLoans)); - public static final Set TRANSFER_CLIENT_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, - dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans)); + public static final Set PROPOSE_CLIENT_TRANSFER_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet(Arrays.asList( + localeParamName, dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans)); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java index a71646c74..b6f7cd3d2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java @@ -80,13 +80,13 @@ public final class TransfersDataValidator { throwExceptionIfValidationWarningsExist(dataValidationErrors); } - public void validateForClientTransferBetweenBranches(final String json) { + public void validateForProposeClientTransferBetweenBranches(final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } final Type typeOfMap = new TypeToken>() {}.getType(); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, - TransferApiConstants.TRANSFER_CLIENT_BETWEEN_BRANCHES_DATA_PARAMETERS); + TransferApiConstants.PROPOSE_CLIENT_TRANSFER_BETWEEN_BRANCHES_DATA_PARAMETERS); final JsonElement element = this.fromApiJsonHelper.parse(json); final List dataValidationErrors = new ArrayList(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientBetweenBranchesCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/AcceptClientTransferCommandHandler.java similarity index 76% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientBetweenBranchesCommandHandler.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/AcceptClientTransferCommandHandler.java index 5d2edea84..756abf1fd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/TransferClientBetweenBranchesCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/AcceptClientTransferCommandHandler.java @@ -14,12 +14,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -public class TransferClientBetweenBranchesCommandHandler implements NewCommandSourceHandler { +public class AcceptClientTransferCommandHandler implements NewCommandSourceHandler { private final TransferWritePlatformService writePlatformService; @Autowired - public TransferClientBetweenBranchesCommandHandler(final TransferWritePlatformService writePlatformService) { + public AcceptClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) { this.writePlatformService = writePlatformService; } @@ -27,6 +27,6 @@ public class TransferClientBetweenBranchesCommandHandler implements NewCommandSo @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.transferClientBetweenBranches(command.entityId(), command); + return this.writePlatformService.acceptClientTransfer(command.entityId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/ProposeClientTransferCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/ProposeClientTransferCommandHandler.java new file mode 100644 index 000000000..c44c7c0f1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/ProposeClientTransferCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.transfer.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.transfer.service.TransferWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class ProposeClientTransferCommandHandler implements NewCommandSourceHandler { + + private final TransferWritePlatformService writePlatformService; + + @Autowired + public ProposeClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.proposeClientTransfer(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/RejectClientTransferCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/RejectClientTransferCommandHandler.java new file mode 100644 index 000000000..faf608d45 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/RejectClientTransferCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.transfer.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.transfer.service.TransferWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class RejectClientTransferCommandHandler implements NewCommandSourceHandler { + + private final TransferWritePlatformService writePlatformService; + + @Autowired + public RejectClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.rejectClientTransfer(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/WithdrawClientTransferCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/WithdrawClientTransferCommandHandler.java new file mode 100644 index 000000000..58e62ea7d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/handler/WithdrawClientTransferCommandHandler.java @@ -0,0 +1,32 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.transfer.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.transfer.service.TransferWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class WithdrawClientTransferCommandHandler implements NewCommandSourceHandler { + + private final TransferWritePlatformService writePlatformService; + + @Autowired + public WithdrawClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.withdrawClientTransfer(command.entityId(), command); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferEventType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferEventType.java new file mode 100644 index 000000000..9c365cd43 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferEventType.java @@ -0,0 +1,29 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.transfer.service; + + +public enum TransferEventType { + PROPOSAL(1, "transferEvent.proposal"), ACCEPTANCE(2, "transferEvent.acceptance"), WITHDRAWAL(3, "transferEvent.withdrawal"), REJECTION( + 4, "transferEvent.rejection"); + + private final Integer value; + private final String code; + + private TransferEventType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformService.java index 6e11e6d46..2b88ad6ed 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformService.java @@ -12,6 +12,12 @@ public interface TransferWritePlatformService { CommandProcessingResult transferClientsBetweenGroups(final Long sourceGroupId, final JsonCommand jsonCommand); - CommandProcessingResult transferClientBetweenBranches(final Long clientId, final JsonCommand jsonCommand); + CommandProcessingResult proposeClientTransfer(final Long clientId, final JsonCommand jsonCommand); + + CommandProcessingResult withdrawClientTransfer(final Long clientId, final JsonCommand jsonCommand); + + CommandProcessingResult acceptClientTransfer(final Long clientId, final JsonCommand jsonCommand); + + CommandProcessingResult rejectClientTransfer(final Long clientId, final JsonCommand jsonCommand); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java index c0892bbc0..3ec065a4f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java @@ -23,6 +23,7 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; +import org.mifosplatform.portfolio.client.domain.ClientStatus; import org.mifosplatform.portfolio.client.exception.ClientHasBeenClosedException; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; @@ -160,7 +161,8 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr } else { Office destinationOffice = destinationGroup.getOffice(); - transferClientToNewBranch(client, destinationOffice); + // handleClientTransfer(client, destinationOffice); + // TODO: Fill in the blanks } /** @@ -208,16 +210,16 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr **/ @Transactional @Override - public CommandProcessingResult transferClientBetweenBranches(final Long clientId, final JsonCommand jsonCommand) { + public CommandProcessingResult proposeClientTransfer(final Long clientId, final JsonCommand jsonCommand) { // validation - this.transfersDataValidator.validateForClientTransferBetweenBranches(jsonCommand.json()); + this.transfersDataValidator.validateForProposeClientTransferBetweenBranches(jsonCommand.json()); final Long destinationOfficeId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationOfficeIdParamName); final Office office = this.officeRepository.findOneWithNotFoundDetection(destinationOfficeId); final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); /*** Remove all group associations for this client ***/ client.getGroups().clear(); - transferClientToNewBranch(client, office); + handleClientTransfer(client, office, TransferEventType.PROPOSAL); this.clientRepository.save(client); return new CommandProcessingResultBuilder() // @@ -226,14 +228,100 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr .build(); } - private void transferClientToNewBranch(final Client client, final Office destinationOffice) { - /** - * For now, do not allow Clients with active loans to be transferred - * between branches - **/ - //if (loanRepository.doesClientHaveActiveLoans(client.getId())) { throw new TransferNotSupportedException(); } - // set the new office for the client - client.updateOffice(destinationOffice); + /** + * This API is meant for transferring clients between branches mainly by + * Organizations following an Individual lending Model
+ * If the Client is linked to any Groups, we can optionally choose to have + * all the linkages broken and all JLG Loans are converted into Individual + * Loans + * + * @param clientId + * @param destinationOfficeId + * @param jsonCommand + * @return + **/ + @Transactional + @Override + public CommandProcessingResult acceptClientTransfer(final Long clientId, final JsonCommand jsonCommand) { + final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); + /*** Remove all group associations for this client ***/ + client.getGroups().clear(); + handleClientTransfer(client, client.getOffice(), TransferEventType.ACCEPTANCE); + this.clientRepository.save(client); + + return new CommandProcessingResultBuilder() // + .withClientId(clientId) // + .withEntityId(clientId) // + .build(); + } + + @Override + @Transactional + public CommandProcessingResult withdrawClientTransfer(Long clientId, JsonCommand jsonCommand) { + final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); + /*** Remove all group associations for this client ***/ + client.getGroups().clear(); + handleClientTransfer(client, client.getOffice(), TransferEventType.WITHDRAWAL); + this.clientRepository.save(client); + + return new CommandProcessingResultBuilder() // + .withClientId(clientId) // + .withEntityId(clientId) // + .build(); + } + + @Override + @Transactional + public CommandProcessingResult rejectClientTransfer(Long clientId, JsonCommand jsonCommand) { + final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); + /*** Remove all group associations for this client ***/ + client.getGroups().clear(); + handleClientTransfer(client, client.getOffice(), TransferEventType.REJECTION); + this.clientRepository.save(client); + + return new CommandProcessingResultBuilder() // + .withClientId(clientId) // + .withEntityId(clientId) // + .build(); + } + + private void handleClientTransfer(final Client client, final Office destinationOffice, TransferEventType transferEventType) { + if (loanRepository.doesClientHaveActiveLoans(client.getId())) { + // get each individual loan for the client + for (Loan loan : loanRepository.findLoanByClientId(client.getId())) { + if (!loan.isClosed()) { + switch (transferEventType) { + case ACCEPTANCE: + this.loanWritePlatformService.acceptLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant()); + break; + case PROPOSAL: + this.loanWritePlatformService.initiateLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant()); + break; + case REJECTION: + this.loanWritePlatformService.rejectLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant()); + break; + case WITHDRAWAL: + this.loanWritePlatformService.withdrawLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant()); + } + } + } + } + + switch (transferEventType) { + case ACCEPTANCE: + client.setStatus(ClientStatus.ACTIVE.getValue()); + break; + case PROPOSAL: + client.setStatus(ClientStatus.TRANSFER_IN_PROGRESS.getValue()); + client.updateOffice(destinationOffice); + break; + case REJECTION: + client.setStatus(ClientStatus.TRANSFER_ON_HOLD.getValue()); + break; + // TODO update client with previous office + case WITHDRAWAL: + client.setStatus(ClientStatus.ACTIVE.getValue()); + } } private List assembleListOfClients(final JsonCommand command) {