Merge pull request #542 from vishwasbabu/mifosx-436

Mifosx 436
This commit is contained in:
Vishwas Babu A J
2013-08-26 10:57:31 -07:00
37 changed files with 764 additions and 103 deletions
@@ -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);
@@ -12,7 +12,8 @@ public class AccountingConstants {
/*** Accounting placeholders for cash based accounting for loan products ***/ /*** Accounting placeholders for cash based accounting for loan products ***/
public static enum CASH_ACCOUNTS_FOR_LOAN { 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; private final Integer value;
@@ -45,7 +46,7 @@ public class AccountingConstants {
/*** Accounting placeholders for accrual based accounting for loan products ***/ /*** Accounting placeholders for accrual based accounting for loan products ***/
public static enum ACCRUAL_ACCOUNTS_FOR_LOAN { 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( 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; private final Integer value;
@@ -84,9 +85,10 @@ public class AccountingConstants {
FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS("interestOnLoanAccountId"), INCOME_FROM_FEES( FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS("interestOnLoanAccountId"), INCOME_FROM_FEES(
"incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), INTEREST_RECEIVABLE( "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), INTEREST_RECEIVABLE(
"receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE( "receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE(
"receivablePenaltyAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), PAYMENT_TYPE( "receivablePenaltyAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING(
"paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( "paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING(
"penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID("incomeAccountId"); "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID(
"incomeAccountId");
private final String value; private final String value;
@@ -77,9 +77,9 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
final Long type = command.longValueOfParameterNamed(GLAccountJsonInputParams.TYPE.getValue()); final Long type = command.longValueOfParameterNamed(GLAccountJsonInputParams.TYPE.getValue());
GLAccountType accountType = GLAccountType.fromInt(type.intValue()); GLAccountType accountType = GLAccountType.fromInt(type.intValue());
if(tagId != null) { if (tagId != null) {
glAccountTagType = retrieveTagId(tagId, accountType); glAccountTagType = retrieveTagId(tagId, accountType);
} }
final GLAccount glAccount = GLAccount.fromJson(parentGLAccount, command, glAccountTagType); final GLAccount glAccount = GLAccount.fromJson(parentGLAccount, command, glAccountTagType);
@@ -104,9 +104,7 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
accountCommand.validateForUpdate(); accountCommand.validateForUpdate();
final Long parentId = command.longValueOfParameterNamed(GLAccountJsonInputParams.PARENT_ID.getValue()); final Long parentId = command.longValueOfParameterNamed(GLAccountJsonInputParams.PARENT_ID.getValue());
if (glAccountId.equals(parentId)) { if (glAccountId.equals(parentId)) { throw new InvalidParentGLAccountHeadException(glAccountId, parentId); }
throw new InvalidParentGLAccountHeadException(glAccountId,parentId);
}
// is the glAccount valid // is the glAccount valid
final GLAccount glAccount = this.glAccountRepository.findOne(glAccountId); final GLAccount glAccount = this.glAccountRepository.findOne(glAccountId);
if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); } if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); }
@@ -120,13 +118,13 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
} }
if (changesOnly.containsKey(GLAccountJsonInputParams.TAGID.getValue())) { if (changesOnly.containsKey(GLAccountJsonInputParams.TAGID.getValue())) {
final Long tagIdLongValue = command.longValueOfParameterNamed(GLAccountJsonInputParams.TAGID.getValue()); final Long tagIdLongValue = command.longValueOfParameterNamed(GLAccountJsonInputParams.TAGID.getValue());
final GLAccountType accountType = GLAccountType.fromInt(glAccount.getType()); final GLAccountType accountType = GLAccountType.fromInt(glAccount.getType());
CodeValue tagID = null; CodeValue tagID = null;
if (tagIdLongValue != null) { if (tagIdLongValue != null) {
tagID = retrieveTagId(tagIdLongValue, accountType); tagID = retrieveTagId(tagIdLongValue, accountType);
} }
glAccount.updateTagId(tagID); glAccount.updateTagId(tagID);
} }
/** /**
@@ -180,11 +178,11 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
*/ */
private GLAccount validateParentGLAccount(final Long parentAccountId) { private GLAccount validateParentGLAccount(final Long parentAccountId) {
GLAccount parentGLAccount = null; GLAccount parentGLAccount = null;
if (parentAccountId != null){ if (parentAccountId != null) {
parentGLAccount = this.glAccountRepository.findOne(parentAccountId); parentGLAccount = this.glAccountRepository.findOne(parentAccountId);
if (parentGLAccount == null) { throw new GLAccountNotFoundException(parentAccountId); } if (parentGLAccount == null) { throw new GLAccountNotFoundException(parentAccountId); }
// ensure parent is not a detail account // ensure parent is not a detail account
if (parentGLAccount.isDetailAccount()) { throw new GLAccountInvalidParentException(parentAccountId); } if (parentGLAccount.isDetailAccount()) { throw new GLAccountInvalidParentException(parentAccountId); }
} }
return parentGLAccount; return parentGLAccount;
} }
@@ -206,24 +204,24 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
} }
private CodeValue retrieveTagId(Long tagId, GLAccountType accountType) { private CodeValue retrieveTagId(Long tagId, GLAccountType accountType) {
CodeValue glAccountTagType = null; CodeValue glAccountTagType = null;
if (accountType.isAssetType()) { if (accountType.isAssetType()) {
glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME, tagId); AccountingConstants.ASSESTS_TAG_OPTION_CODE_NAME, tagId);
} else if (accountType.isLiabilityType()) { } else if (accountType.isLiabilityType()) {
glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME, tagId); AccountingConstants.LIABILITIES_TAG_OPTION_CODE_NAME, tagId);
} else if (accountType.isEquityType()) { } else if (accountType.isEquityType()) {
glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME, tagId); AccountingConstants.EQUITY_TAG_OPTION_CODE_NAME, tagId);
} else if (accountType.isIncomeType()) { } else if (accountType.isIncomeType()) {
glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
AccountingConstants.INCOME_TAG_OPTION_CODE_NAME, tagId); AccountingConstants.INCOME_TAG_OPTION_CODE_NAME, tagId);
} else if (accountType.isExpenseType()) { } else if (accountType.isExpenseType()) {
glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection( glAccountTagType = this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(
AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME, tagId); AccountingConstants.EXPENSES_TAG_OPTION_CODE_NAME, tagId);
} }
return glAccountTagType; return glAccountTagType;
} }
} }
@@ -63,6 +63,9 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF
helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF, helper.createCashBasedJournalEntriesAndReversalsForLoan(office, CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF,
CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, paymentTypeId, loanId, transactionId, transactionDate, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
principalAmount, false); 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, helper.createDebitJournalEntryOrReversalForLoan(office, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE, loanProductId, paymentTypeId, loanId,
transactionId, transactionDate, totalDebitAmount, isReversal); 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 <br/>
*
* 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);
}
}
} }
@@ -167,6 +167,8 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.toString(), changes); 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, 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); 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 // income
mergeLoanToIncomeAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), loanProductId, 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); 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, 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); 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(), mergeLoanToAssetAccountMappingChanges(element, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(),
loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), loanProductId, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(),
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(), changes); ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.toString(), changes);
@@ -102,6 +102,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), glAccountId); accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), glAccountId);
} else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) { } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), glAccountId); 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)) { } else if (glAccountForLoan.equals(CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), glAccountId); 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); accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), glAccountId);
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) { } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), glAccountId); 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)) { } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF)) {
accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), glAccountId); accountMappingDetails.put(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), glAccountId);
} else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE)) { } else if (glAccountForLoan.equals(ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE)) {
@@ -63,6 +63,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId,
CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); 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 // income
loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element,
@@ -93,6 +96,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element, loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId, LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue()); 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, loanProductToGLAccountMappingHelper.saveLoanToAssetAccountMapping(element,
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), loanProductId, LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), loanProductId,
ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue()); ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue());
@@ -564,8 +564,20 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT"); return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT");
} }
public boolean isClientTransfer() { public boolean isProposeClientTransfer() {
return this.actionName.equalsIgnoreCase("TRANSFER") && this.entityName.equalsIgnoreCase("CLIENT"); 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() { public boolean isProductMixResource() {
@@ -352,12 +352,39 @@ public class CommandWrapperBuilder {
return this; return this;
} }
public CommandWrapperBuilder transferClient(final Long clientId) { public CommandWrapperBuilder proposeClientTransfer(final Long clientId) {
this.actionName = "TRANSFER"; this.actionName = "PROPOSETRANSFER";
this.entityName = "CLIENT"; this.entityName = "CLIENT";
this.entityId = clientId; this.entityId = clientId;
this.clientId = 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; return this;
} }
@@ -170,8 +170,14 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = this.applicationContext.getBean("assignClientStaffCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("assignClientStaffCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isClientClose()) { } else if (wrapper.isClientClose()) {
handler = this.applicationContext.getBean("closeClientCommandHandler", NewCommandSourceHandler.class); handler = this.applicationContext.getBean("closeClientCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isClientTransfer()) { } else if (wrapper.isProposeClientTransfer()) {
handler = this.applicationContext.getBean("transferClientBetweenBranchesCommandHandler", NewCommandSourceHandler.class); 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 { } else {
throw new UnsupportedCommandException(wrapper.commandName()); throw new UnsupportedCommandException(wrapper.commandName());
} }
@@ -207,13 +207,22 @@ public class ClientsApiResource {
} else if (is(commandParam, "close")) { } else if (is(commandParam, "close")) {
commandRequest = builder.closeClient(clientId).build(); commandRequest = builder.closeClient(clientId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}else if (is(commandParam, "transfer")) { } else if (is(commandParam, "proposeTransfer")) {
commandRequest = builder.transferClient(clientId).build(); 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); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} }
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", 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); return this.toApiJsonSerializer.serialize(result);
} }
@@ -253,6 +253,10 @@ public final class Client extends AbstractPersistable<Long> {
return ClientStatus.fromInt(this.status).isActive(); return ClientStatus.fromInt(this.status).isActive();
} }
public boolean isTransferInProgress () {
return ClientStatus.fromInt(this.status).isTransferInProgress();
}
public boolean isNotPending() { public boolean isNotPending() {
return !isPending(); return !isPending();
} }
@@ -473,6 +477,10 @@ public final class Client extends AbstractPersistable<Long> {
return this.status; return this.status;
} }
public void setStatus(Integer status) {
this.status = status;
}
public boolean isActivatedAfter(final LocalDate submittedOn) { public boolean isActivatedAfter(final LocalDate submittedOn) {
return getActivationLocalDate().isAfter(submittedOn); return getActivationLocalDate().isAfter(submittedOn);
} }
@@ -29,6 +29,16 @@ public class ClientEnumerations {
case CLOSED: case CLOSED:
optionData = new EnumOptionData(ClientStatus.CLOSED.getValue().longValue(), ClientStatus.CLOSED.getCode(), "Closed"); optionData = new EnumOptionData(ClientStatus.CLOSED.getValue().longValue(), ClientStatus.CLOSED.getCode(), "Closed");
break; 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; return optionData;
@@ -13,6 +13,8 @@ public enum ClientStatus {
INVALID(0, "clientStatusType.invalid"), // INVALID(0, "clientStatusType.invalid"), //
PENDING(100, "clientStatusType.pending"), // PENDING(100, "clientStatusType.pending"), //
ACTIVE(300, "clientStatusType.active"), // ACTIVE(300, "clientStatusType.active"), //
TRANSFER_IN_PROGRESS(303, "clientStatusType.transfer.in.progress"), //
TRANSFER_ON_HOLD(304, "clientStatusType.transfer.on.hold"), //
CLOSED(600, "clientStatusType.closed"); CLOSED(600, "clientStatusType.closed");
private final Integer value; private final Integer value;
@@ -27,6 +29,12 @@ public enum ClientStatus {
break; break;
case 300: case 300:
enumeration = ClientStatus.ACTIVE; enumeration = ClientStatus.ACTIVE;
break;
case 303:
enumeration = ClientStatus.TRANSFER_IN_PROGRESS;
break;
case 304:
enumeration = ClientStatus.TRANSFER_ON_HOLD;
break; break;
case 600: case 600:
enumeration = ClientStatus.CLOSED; enumeration = ClientStatus.CLOSED;
@@ -63,4 +71,16 @@ public enum ClientStatus {
public boolean isClosed() { public boolean isClosed() {
return this.value.equals(ClientStatus.CLOSED.getValue()); 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();
}
} }
@@ -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);
}
}
@@ -38,6 +38,7 @@ import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.client.domain.Client; 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.api.GroupingTypesApiConstants;
import org.mifosplatform.portfolio.group.exception.ClientExistInGroupException; import org.mifosplatform.portfolio.group.exception.ClientExistInGroupException;
import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException; import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException;
@@ -385,6 +386,10 @@ public final class Group extends AbstractPersistable<Long> {
return this.groupLevel.isCenter(); return this.groupLevel.isCenter();
} }
public boolean isTransferInProgress () {
return GroupingTypeStatus.fromInt(this.status).isTransferInProgress();
}
public boolean isChildClient(final Long clientId){ public boolean isChildClient(final Long clientId){
if (clientId != null && this.clientMembers != null && !this.clientMembers.isEmpty()) { if (clientId != null && this.clientMembers != null && !this.clientMembers.isEmpty()) {
for (Client client : this.clientMembers) { for (Client client : this.clientMembers) {
@@ -13,6 +13,8 @@ public enum GroupingTypeStatus {
INVALID(0, "groupingStatusType.invalid"), // INVALID(0, "groupingStatusType.invalid"), //
PENDING(100, "groupingStatusType.pending"), // PENDING(100, "groupingStatusType.pending"), //
ACTIVE(300, "groupingStatusType.active"), // ACTIVE(300, "groupingStatusType.active"), //
TRANSFER_IN_PROGRESS(303, "clientStatusType.transfer.in.progress"), //
TRANSFER_ON_HOLD(304, "clientStatusType.transfer.on.hold"), //
CLOSED(600, "groupingStatusType.closed"); CLOSED(600, "groupingStatusType.closed");
private final Integer value; private final Integer value;
@@ -63,4 +65,16 @@ public enum GroupingTypeStatus {
public boolean isClosed() { public boolean isClosed() {
return this.value.equals(GroupingTypeStatus.CLOSED.getValue()); 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();
}
} }
@@ -25,6 +25,10 @@ public class LoanTransactionEnumData {
private final boolean applyCharges; private final boolean applyCharges;
private final boolean writeOff; private final boolean writeOff;
private final boolean recoveryRepayment; 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) { public LoanTransactionEnumData(final Long id, final String code, final String value) {
this.id = id; this.id = id;
@@ -40,6 +44,10 @@ public class LoanTransactionEnumData {
this.applyInterest = Long.valueOf(11).equals(this.id); this.applyInterest = Long.valueOf(11).equals(this.id);
this.writeOff = Long.valueOf(6).equals(this.id); this.writeOff = Long.valueOf(6).equals(this.id);
this.recoveryRepayment = Long.valueOf(8).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() { public Long id() {
@@ -93,4 +101,20 @@ public class LoanTransactionEnumData {
return this.applyInterest; 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;
}
} }
@@ -1281,7 +1281,7 @@ public class Loan extends AbstractPersistable<Long> {
return actualChanges; return actualChanges;
} }
private Collection<Long> findExistingTransactionIds() { public Collection<Long> findExistingTransactionIds() {
final Collection<Long> ids = new ArrayList<Long>(); final Collection<Long> ids = new ArrayList<Long>();
@@ -1292,7 +1292,7 @@ public class Loan extends AbstractPersistable<Long> {
return ids; return ids;
} }
private Collection<Long> findExistingReversedTransactionIds() { public Collection<Long> findExistingReversedTransactionIds() {
final Collection<Long> ids = new ArrayList<Long>(); final Collection<Long> ids = new ArrayList<Long>();
@@ -2011,7 +2011,7 @@ public class Loan extends AbstractPersistable<Long> {
return hasDisbursementTransaction(); return hasDisbursementTransaction();
} }
private boolean isClosed() { public boolean isClosed() {
return status().isClosed() || isCancelled(); return status().isClosed() || isCancelled();
} }
@@ -2579,6 +2579,14 @@ public class Loan extends AbstractPersistable<Long> {
return this.termPeriodFrequencyType; return this.termPeriodFrequencyType;
} }
public List<LoanTransaction> getLoanTransactions() {
return this.loanTransactions;
}
public void setLoanStatus(Integer loanStatus) {
this.loanStatus = loanStatus;
}
public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final WorkingDays workingDays, public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final WorkingDays workingDays,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final boolean allowTransactionsOnNonWorkingDay) { final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final boolean allowTransactionsOnNonWorkingDay) {
// validate if disbursement date is a holiday or a non-working day // validate if disbursement date is a holiday or a non-working day
@@ -52,7 +52,7 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
public static final String FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP = "Select loan.loanProduct.id from Loan loan where " 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"; + "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) @Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId, List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
@@ -14,6 +14,8 @@ public enum LoanStatus {
SUBMITTED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), // SUBMITTED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
APPROVED(200, "loanStatusType.approved"), // APPROVED(200, "loanStatusType.approved"), //
ACTIVE(300, "loanStatusType.active"), // 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"), // WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), //
REJECTED(500, "loanStatusType.rejected"), // REJECTED(500, "loanStatusType.rejected"), //
CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), // CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), //
@@ -37,6 +39,12 @@ public enum LoanStatus {
case 300: case 300:
enumeration = LoanStatus.ACTIVE; enumeration = LoanStatus.ACTIVE;
break; break;
case 303:
enumeration = LoanStatus.TRANSFER_IN_PROGRESS;
break;
case 304:
enumeration = LoanStatus.TRANSFER_ON_HOLD;
break;
case 400: case 400:
enumeration = LoanStatus.WITHDRAWN_BY_CLIENT; enumeration = LoanStatus.WITHDRAWN_BY_CLIENT;
break; break;
@@ -115,4 +123,16 @@ public enum LoanStatus {
public boolean isActiveOrAwaitingApprovalOrDisbursal() { public boolean isActiveOrAwaitingApprovalOrDisbursal() {
return this.isApproved() || this.isSubmittedAndPendingApproval() || this.isActive(); 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();
}
} }
@@ -137,6 +137,32 @@ public final class LoanSummary {
return this.totalInterestCharged; 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 <code>totalFeeChargesDueAtDisbursement</code> should be * All fields but <code>totalFeeChargesDueAtDisbursement</code> should be
* reset. * reset.
@@ -123,6 +123,20 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
return new LoanTransaction(loan, office, LoanTransactionType.APPLY_INTEREST, amount.getAmount(), interestAppliedDate, null); 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) { public static LoanTransaction copyTransactionProperties(LoanTransaction loanTransaction) {
return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf, return new LoanTransaction(loanTransaction.loan, loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf,
loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion, loanTransaction.amount, loanTransaction.principalPortion, loanTransaction.interestPortion,
@@ -22,7 +22,17 @@ public enum LoanTransactionType {
RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), // RECOVERY_REPAYMENT(8, "loanTransactionType.recoveryRepayment"), //
WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), // WAIVE_CHARGES(9, "loanTransactionType.waiveCharges"), //
APPLY_CHARGES(10, "loanTransactionType.applyCharges"), // 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 Integer value;
private final String code; private final String code;
@@ -79,6 +89,18 @@ public enum LoanTransactionType {
case 11: case 11:
loanTransactionType = LoanTransactionType.APPLY_INTEREST; loanTransactionType = LoanTransactionType.APPLY_INTEREST;
break; 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: default:
loanTransactionType = LoanTransactionType.INVALID; loanTransactionType = LoanTransactionType.INVALID;
break; break;
@@ -8,12 +8,14 @@ package org.mifosplatform.portfolio.loanaccount.service;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
public interface LoanWritePlatformService { public interface LoanWritePlatformService {
@@ -54,4 +56,12 @@ public interface LoanWritePlatformService {
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances); void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
void applyHolidaysToLoans(); 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);
} }
@@ -51,6 +51,7 @@ import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedExce
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException; import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; 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.CollectionSheetBulkDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand; import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand;
@@ -892,6 +893,64 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return loanCharge; return loanCharge;
} }
@Transactional
@Override
public LoanTransaction initiateLoanTransfer(final Long accountId, final LocalDate TransferDate) {
final Loan loan = this.loanAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan);
final List<Long> existingTransactionIds = new ArrayList<Long>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(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<Long> existingTransactionIds = new ArrayList<Long>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(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 @Transactional
@Override @Override
public CommandProcessingResult loanReassignment(final Long loanId, final JsonCommand command) { 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()); } 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()); }
}
}
} }
@@ -38,17 +38,17 @@ public final class LoanProductDataValidator {
/** /**
* The parameters supported for this command. * The parameters supported for this command.
*/ */
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("locale", "dateFormat", "name", "description", "fundId", private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("locale", "dateFormat", "name", "description",
"currencyCode", "digitsAfterDecimal","inMultiplesOf", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", "numberOfRepayments", "fundId", "currencyCode", "digitsAfterDecimal", "inMultiplesOf", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery",
"minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod", "numberOfRepayments", "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod",
"minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType", "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType",
"interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "graceOnPrincipalPayment", "interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "graceOnPrincipalPayment",
"graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule","includeInBorrowerCycle", "startDate", "closeDate", "externalId", "graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule", "includeInBorrowerCycle", "startDate",
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), "closeDate", "externalId", LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTIES_RECEIVABLE.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.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue())); LOAN_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue()));
@@ -83,7 +83,8 @@ public final class LoanProductDataValidator {
} }
final Boolean includeInBorrowerCycle = fromApiJsonHelper.extractBooleanNamed("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();
// terms // terms
final String currencyCode = fromApiJsonHelper.extractStringNamed("currencyCode", element); final String currencyCode = fromApiJsonHelper.extractStringNamed("currencyCode", element);
@@ -252,6 +253,11 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId)
.notNull().integerGreaterThanZero(); .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( final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed(
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element); LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId)
@@ -325,7 +331,8 @@ public final class LoanProductDataValidator {
if (fromApiJsonHelper.parameterExists("includeInBorrowerCycle", element)) { if (fromApiJsonHelper.parameterExists("includeInBorrowerCycle", element)) {
final Boolean includeInBorrowerCycle = fromApiJsonHelper.extractBooleanNamed("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)) { if (fromApiJsonHelper.parameterExists("currencyCode", element)) {
@@ -478,6 +485,11 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue()).value(loanPortfolioAccountId)
.ignoreIfNull().integerGreaterThanZero(); .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(), final Long incomeFromInterestId = fromApiJsonHelper.extractLongNamed(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(),
element); element);
baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId) baseDataValidator.reset().parameter(LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue()).value(incomeFromInterestId)
@@ -16,7 +16,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodM
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; 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 LOAN_TERM_FREQUENCY_TYPE = "loanTermFrequencyType";
public static final String TERM_FREQUENCY_TYPE = "termFrequencyType"; public static final String TERM_FREQUENCY_TYPE = "termFrequencyType";
@@ -282,6 +282,24 @@ public class LoanEnumerations{
optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_INTEREST.getValue().longValue(), optionData = new LoanTransactionEnumData(LoanTransactionType.APPLY_INTEREST.getValue().longValue(),
LoanTransactionType.APPLY_INTEREST.getCode(), "Apply Interest"); LoanTransactionType.APPLY_INTEREST.getCode(), "Apply Interest");
break; 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; return optionData;
} }
@@ -329,6 +347,16 @@ public class LoanEnumerations{
case OVERPAID: case OVERPAID:
optionData = new LoanStatusEnumData(LoanStatus.OVERPAID.getValue().longValue(), LoanStatus.OVERPAID.getCode(), "Overpaid"); optionData = new LoanStatusEnumData(LoanStatus.OVERPAID.getValue().longValue(), LoanStatus.OVERPAID.getCode(), "Overpaid");
break; 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; return optionData;
@@ -28,7 +28,7 @@ public class TransferApiConstants {
dateFormatParamName, destinationGroupIdParamName, clients, inheritDestinationGroupLoanOfficer, newStaffIdParamName, dateFormatParamName, destinationGroupIdParamName, clients, inheritDestinationGroupLoanOfficer, newStaffIdParamName,
transferActiveLoans)); transferActiveLoans));
public static final Set<String> TRANSFER_CLIENT_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName, public static final Set<String> PROPOSE_CLIENT_TRANSFER_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans)); localeParamName, dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans));
} }
@@ -80,13 +80,13 @@ public final class TransfersDataValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
public void validateForClientTransferBetweenBranches(final String json) { public void validateForProposeClientTransferBetweenBranches(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, 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 JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
@@ -14,12 +14,12 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
public class TransferClientBetweenBranchesCommandHandler implements NewCommandSourceHandler { public class AcceptClientTransferCommandHandler implements NewCommandSourceHandler {
private final TransferWritePlatformService writePlatformService; private final TransferWritePlatformService writePlatformService;
@Autowired @Autowired
public TransferClientBetweenBranchesCommandHandler(final TransferWritePlatformService writePlatformService) { public AcceptClientTransferCommandHandler(final TransferWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService; this.writePlatformService = writePlatformService;
} }
@@ -27,6 +27,6 @@ public class TransferClientBetweenBranchesCommandHandler implements NewCommandSo
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.transferClientBetweenBranches(command.entityId(), command); return this.writePlatformService.acceptClientTransfer(command.entityId(), command);
} }
} }
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -12,6 +12,12 @@ public interface TransferWritePlatformService {
CommandProcessingResult transferClientsBetweenGroups(final Long sourceGroupId, final JsonCommand jsonCommand); 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);
} }
@@ -23,6 +23,7 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; 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.client.exception.ClientHasBeenClosedException;
import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
@@ -160,7 +161,8 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr
} else { } else {
Office destinationOffice = destinationGroup.getOffice(); 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 @Transactional
@Override @Override
public CommandProcessingResult transferClientBetweenBranches(final Long clientId, final JsonCommand jsonCommand) { public CommandProcessingResult proposeClientTransfer(final Long clientId, final JsonCommand jsonCommand) {
// validation // validation
this.transfersDataValidator.validateForClientTransferBetweenBranches(jsonCommand.json()); this.transfersDataValidator.validateForProposeClientTransferBetweenBranches(jsonCommand.json());
final Long destinationOfficeId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationOfficeIdParamName); final Long destinationOfficeId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationOfficeIdParamName);
final Office office = this.officeRepository.findOneWithNotFoundDetection(destinationOfficeId); final Office office = this.officeRepository.findOneWithNotFoundDetection(destinationOfficeId);
final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
/*** Remove all group associations for this client ***/ /*** Remove all group associations for this client ***/
client.getGroups().clear(); client.getGroups().clear();
transferClientToNewBranch(client, office); handleClientTransfer(client, office, TransferEventType.PROPOSAL);
this.clientRepository.save(client); this.clientRepository.save(client);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -226,14 +228,100 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr
.build(); .build();
} }
private void transferClientToNewBranch(final Client client, final Office destinationOffice) { /**
/** * This API is meant for transferring clients between branches mainly by
* For now, do not allow Clients with active loans to be transferred * Organizations following an Individual lending Model <br/>
* between branches * 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
//if (loanRepository.doesClientHaveActiveLoans(client.getId())) { throw new TransferNotSupportedException(); } * Loans
// set the new office for the client *
client.updateOffice(destinationOffice); * @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<Client> assembleListOfClients(final JsonCommand command) { private List<Client> assembleListOfClients(final JsonCommand command) {