accounting wip : completed cash based posting includes writeoffs and adjustments
This commit is contained in:
+105
-41
@@ -128,6 +128,18 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
|
|||||||
@Override
|
@Override
|
||||||
public void createJournalEntriesForLoan(Loan loan, List<LoanTransaction> loanTransactions) {
|
public void createJournalEntriesForLoan(Loan loan, List<LoanTransaction> loanTransactions) {
|
||||||
// shouldn't be before an accounting closure
|
// shouldn't be before an accounting closure
|
||||||
|
if (loan.isCashBasedAccountingEnabledOnLoanProduct()) {
|
||||||
|
createJournalEntriesUsingCashRules(loan, loanTransactions);
|
||||||
|
} else if (loan.isAccrualBasedAccountingEnabledOnLoanProduct()) {
|
||||||
|
// TODO Vishwas: add accrual based accounting rules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loan
|
||||||
|
* @param loanTransactions
|
||||||
|
*/
|
||||||
|
private void createJournalEntriesUsingCashRules(Loan loan, List<LoanTransaction> loanTransactions) {
|
||||||
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(loan.getOfficeId());
|
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(loan.getOfficeId());
|
||||||
Office office = loan.getClient().getOffice();
|
Office office = loan.getClient().getOffice();
|
||||||
Long loanProductId = loan.productId();
|
Long loanProductId = loan.productId();
|
||||||
@@ -141,9 +153,11 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
|
|||||||
* the transaction Date
|
* the transaction Date
|
||||||
**/
|
**/
|
||||||
if (latestGLClosure != null) {
|
if (latestGLClosure != null) {
|
||||||
if (latestGLClosure.getClosingDate().after(loanTransaction.getDateOf())) { throw new GLJournalEntryInvalidException(
|
if (latestGLClosure.getClosingDate().after(loanTransaction.getDateOf())
|
||||||
|
|| latestGLClosure.getClosingDate().equals(loanTransaction.getDateOf())) { throw new GLJournalEntryInvalidException(
|
||||||
GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); }
|
GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Debit loan Portfolio and credit Fund source for Disbursal **/
|
/*** Debit loan Portfolio and credit Fund source for Disbursal **/
|
||||||
if (loanTransaction.isDisbursement()) {
|
if (loanTransaction.isDisbursement()) {
|
||||||
|
|
||||||
@@ -153,54 +167,104 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
|
|||||||
BigDecimal disbursalAmount = loanTransaction.getAmount();
|
BigDecimal disbursalAmount = loanTransaction.getAmount();
|
||||||
createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, disbursalAmount);
|
createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, disbursalAmount);
|
||||||
createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, disbursalAmount);
|
createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, disbursalAmount);
|
||||||
|
} else if (loanTransaction.isRepayment() || loanTransaction.isRepaymentAtDisbursement() || loanTransaction.isContra()) {
|
||||||
|
createJournalEntriesForRepaymentOrContraUsingCashRules(loan, office, loanProductId, loanTransaction, entryDate,
|
||||||
|
transactionId, loanId);
|
||||||
}/***
|
}/***
|
||||||
* Create a single Debit to fund source and multiple credits if
|
* Only principal write off affects cash based accounting (interest
|
||||||
* applicable (loan portfolio for principal repayment, Interest on
|
* and fee write off need not be considered). Debit losses written
|
||||||
* loans for interest repayments, Income from fees for fees payment
|
* off and credit Loan Portfolio
|
||||||
* and Income from penalties for penalty payment)
|
|
||||||
**/
|
**/
|
||||||
else if (loanTransaction.isRepayment() || loanTransaction.isRepaymentAtDisbursement()) {
|
else if (loanTransaction.isWriteOff()) {
|
||||||
BigDecimal principalAmount = loanTransaction.getPrincipalPortion(loan.getCurrency()).getAmount();
|
BigDecimal principalAmount = loanTransaction.getPrincipalPortion(loan.getCurrency()).getAmount();
|
||||||
BigDecimal interestAmount = loanTransaction.getInterestPortion(loan.getCurrency()).getAmount();
|
GLAccount lossesWrittenOffAccount = getLinkedFinAccountForLoanProduct(loanProductId,
|
||||||
BigDecimal feesAmount = loanTransaction.getFeeChargesPortion(loan.getCurrency()).getAmount();
|
CASH_ACCOUNTS_FOR_LOAN.LOSSES_WRITTEN_OFF);
|
||||||
BigDecimal penaltiesAmount = loanTransaction.getPenaltyChargesPortion(loan.getCurrency()).getAmount();
|
createDebitJournalEntryForLoanProduct(office, lossesWrittenOffAccount, loanId, transactionId, entryDate, principalAmount);
|
||||||
|
|
||||||
BigDecimal totalDebitAmount = new BigDecimal(0);
|
GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
||||||
|
createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
/**
|
||||||
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
* Create a single Debit to fund source and multiple credits if applicable
|
||||||
|
* (loan portfolio for principal repayment, Interest on loans for interest
|
||||||
|
* repayments, Income from fees for fees payment and Income from penalties
|
||||||
|
* for penalty payment)
|
||||||
|
*
|
||||||
|
* In case the loan transaction is a contra, all debits are turned into
|
||||||
|
* credits and vice versa
|
||||||
|
*
|
||||||
|
* @param loan
|
||||||
|
* @param office
|
||||||
|
* @param loanProductId
|
||||||
|
* @param loanTransaction
|
||||||
|
* @param entryDate
|
||||||
|
* @param transactionId
|
||||||
|
* @param loanId
|
||||||
|
*/
|
||||||
|
private void createJournalEntriesForRepaymentOrContraUsingCashRules(Loan loan, Office office, Long loanProductId,
|
||||||
|
LoanTransaction loanTransaction, Date entryDate, String transactionId, Long loanId) {
|
||||||
|
BigDecimal principalAmount = loanTransaction.getPrincipalPortion(loan.getCurrency()).getAmount();
|
||||||
|
BigDecimal interestAmount = loanTransaction.getInterestPortion(loan.getCurrency()).getAmount();
|
||||||
|
BigDecimal feesAmount = loanTransaction.getFeeChargesPortion(loan.getCurrency()).getAmount();
|
||||||
|
BigDecimal penaltiesAmount = loanTransaction.getPenaltyChargesPortion(loan.getCurrency()).getAmount();
|
||||||
|
|
||||||
GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
BigDecimal totalDebitAmount = new BigDecimal(0);
|
||||||
createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount);
|
|
||||||
}
|
|
||||||
if (!(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
|
||||||
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
|
||||||
|
|
||||||
GLAccount interestAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS);
|
if (loanTransaction.isContra()) {
|
||||||
createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, entryDate, interestAmount);
|
transactionId = "reversal-" + transactionId;
|
||||||
}
|
|
||||||
if (!(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
|
||||||
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
|
||||||
|
|
||||||
GLAccount incomeFromFeesAccount = getLinkedFinAccountForLoanProduct(loanProductId,
|
|
||||||
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES);
|
|
||||||
createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, entryDate, feesAmount);
|
|
||||||
}
|
|
||||||
if (!(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
|
||||||
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
|
||||||
|
|
||||||
GLAccount incomeFromPenaltiesAccount = getLinkedFinAccountForLoanProduct(loanProductId,
|
|
||||||
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES);
|
|
||||||
createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, entryDate,
|
|
||||||
penaltiesAmount);
|
|
||||||
}
|
|
||||||
GLAccount fundSourceAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE);
|
|
||||||
createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, totalDebitAmount);
|
|
||||||
} // TODO Vishwas: Add conditions for write off, contra waive
|
|
||||||
// interest
|
|
||||||
// and waive charges
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(principalAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||||
|
totalDebitAmount = totalDebitAmount.add(principalAmount);
|
||||||
|
|
||||||
|
GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
|
||||||
|
if (loanTransaction.isContra()) {
|
||||||
|
createDebitJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount);
|
||||||
|
} else {
|
||||||
|
createCreditJournalEntryForLoanProduct(office, loanPortfolioAccount, loanId, transactionId, entryDate, principalAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(interestAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||||
|
totalDebitAmount = totalDebitAmount.add(interestAmount);
|
||||||
|
|
||||||
|
GLAccount interestAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.INTEREST_ON_LOANS);
|
||||||
|
if (loanTransaction.isContra()) {
|
||||||
|
createDebitJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, entryDate, interestAmount);
|
||||||
|
} else {
|
||||||
|
createCreditJournalEntryForLoanProduct(office, interestAccount, loanId, transactionId, entryDate, interestAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(feesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||||
|
totalDebitAmount = totalDebitAmount.add(feesAmount);
|
||||||
|
|
||||||
|
GLAccount incomeFromFeesAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES);
|
||||||
|
if (loanTransaction.isContra()) {
|
||||||
|
createDebitJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, entryDate, feesAmount);
|
||||||
|
} else {
|
||||||
|
createCreditJournalEntryForLoanProduct(office, incomeFromFeesAccount, loanId, transactionId, entryDate, feesAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) {
|
||||||
|
totalDebitAmount = totalDebitAmount.add(penaltiesAmount);
|
||||||
|
|
||||||
|
GLAccount incomeFromPenaltiesAccount = getLinkedFinAccountForLoanProduct(loanProductId,
|
||||||
|
CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES);
|
||||||
|
if (loanTransaction.isContra()) {
|
||||||
|
createDebitJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, entryDate, penaltiesAmount);
|
||||||
|
} else {
|
||||||
|
createCreditJournalEntryForLoanProduct(office, incomeFromPenaltiesAccount, loanId, transactionId, entryDate,
|
||||||
|
penaltiesAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GLAccount fundSourceAccount = getLinkedFinAccountForLoanProduct(loanProductId, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE);
|
||||||
|
if (loanTransaction.isContra()) {
|
||||||
|
createCreditJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, totalDebitAmount);
|
||||||
|
} else {
|
||||||
|
createDebitJournalEntryForLoanProduct(office, fundSourceAccount, loanId, transactionId, entryDate, totalDebitAmount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -246,7 +310,7 @@ public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJo
|
|||||||
// shouldn't be before an accounting closure
|
// shouldn't be before an accounting closure
|
||||||
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId());
|
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId());
|
||||||
if (latestGLClosure != null) {
|
if (latestGLClosure != null) {
|
||||||
if (latestGLClosure.getClosingDate().after(entryDate)) { throw new GLJournalEntryInvalidException(
|
if (latestGLClosure.getClosingDate().after(entryDate) || latestGLClosure.getClosingDate().equals(entryDate)) { throw new GLJournalEntryInvalidException(
|
||||||
GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); }
|
GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); }
|
||||||
}
|
}
|
||||||
/*** check if credits and debits are valid **/
|
/*** check if credits and debits are valid **/
|
||||||
|
|||||||
+8
@@ -2003,6 +2003,14 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
public boolean isAccountingEnabledOnLoanProduct() {
|
public boolean isAccountingEnabledOnLoanProduct() {
|
||||||
return this.loanProduct.isAccountingEnabled();
|
return this.loanProduct.isAccountingEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCashBasedAccountingEnabledOnLoanProduct(){
|
||||||
|
return this.loanProduct.isCashBasedAccountingEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccrualBasedAccountingEnabledOnLoanProduct(){
|
||||||
|
return this.loanProduct.isAccrualBasedAccountingEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
public Long productId() {
|
public Long productId() {
|
||||||
return this.loanProduct.getId();
|
return this.loanProduct.getId();
|
||||||
|
|||||||
+4
@@ -146,6 +146,10 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
public boolean isNotContra() {
|
public boolean isNotContra() {
|
||||||
return this.contra == null;
|
return this.contra == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isContra() {
|
||||||
|
return this.contra != null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDisbursement() {
|
public boolean isDisbursement() {
|
||||||
return LoanTransactionType.DISBURSEMENT.equals(getTypeOf());
|
return LoanTransactionType.DISBURSEMENT.equals(getTypeOf());
|
||||||
|
|||||||
+21
-4
@@ -122,8 +122,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
// variable stores Id's of all existing loan transactions (newly created
|
// variable stores Id's of all existing loan transactions (newly created
|
||||||
// loan transactions would not have an Id before save)
|
// loan transactions would not have an Id before save)
|
||||||
Set<Long> existingLoanTransactionIds = new HashSet<Long>();
|
Set<Long> existingLoanTransactionIds = new HashSet<Long>();
|
||||||
for (LoanTransaction loanTransaction : loan.getLoanTransactions()) {
|
for(LoanTransaction loanTransaction:loan.getLoanTransactions()){
|
||||||
if (loanTransaction.getId() == null) {
|
if(!(loanTransaction.getId()==null)){
|
||||||
existingLoanTransactionIds.add(loanTransaction.getId());
|
existingLoanTransactionIds.add(loanTransaction.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,13 +261,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
|
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
|
||||||
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||||
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
|
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
|
||||||
|
|
||||||
|
boolean transactionIsRepayment= transactionToAdjust.isRepayment();
|
||||||
|
|
||||||
final LoanTransaction newTransactionDetail = loan.adjustExistingTransaction(transactionDate, transactionAmount,
|
final LoanTransaction newTransactionDetail = loan.adjustExistingTransaction(transactionDate, transactionAmount,
|
||||||
defaultLoanLifecycleStateMachine(), transactionToAdjust);
|
defaultLoanLifecycleStateMachine(), transactionToAdjust);
|
||||||
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
|
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
|
||||||
this.loanTransactionRepository.save(newTransactionDetail);
|
this.loanTransactionRepository.save(newTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loanRepository.save(loan);
|
this.loanRepository.save(loan);
|
||||||
|
|
||||||
final String noteText = command.stringValueOfParameterNamed("note");
|
final String noteText = command.stringValueOfParameterNamed("note");
|
||||||
@@ -279,7 +281,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
changes.put("locale", command.locale());
|
changes.put("locale", command.locale());
|
||||||
changes.put("dateFormat", command.dateFormat());
|
changes.put("dateFormat", command.dateFormat());
|
||||||
|
|
||||||
|
// make a call to accounting only if transaction being adjusted is a repayment (not waiver)
|
||||||
|
if (loan.isAccountingEnabledOnLoanProduct() && transactionIsRepayment) {
|
||||||
|
List<LoanTransaction> newLoanTransactions = new ArrayList<LoanTransaction>();
|
||||||
|
if (newTransactionDetail.getId() != null) {
|
||||||
|
newLoanTransactions.add(newTransactionDetail);
|
||||||
|
}
|
||||||
|
newLoanTransactions.add(transactionToAdjust);
|
||||||
|
journalEntryWritePlatformService.createJournalEntriesForLoan(loan, newLoanTransactions);
|
||||||
|
}
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder() //
|
||||||
.withCommandId(command.commandId()) //
|
.withCommandId(command.commandId()) //
|
||||||
.withEntityId(transactionId) //
|
.withEntityId(transactionId) //
|
||||||
@@ -361,6 +373,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
changes.put("locale", command.locale());
|
changes.put("locale", command.locale());
|
||||||
changes.put("dateFormat", command.dateFormat());
|
changes.put("dateFormat", command.dateFormat());
|
||||||
|
|
||||||
|
// make a call to accounting
|
||||||
|
if (loan.isAccountingEnabledOnLoanProduct()) {
|
||||||
|
journalEntryWritePlatformService.createJournalEntriesForLoan(loan, writeoff);
|
||||||
|
}
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder() //
|
||||||
.withCommandId(command.commandId()) //
|
.withCommandId(command.commandId()) //
|
||||||
|
|||||||
+9
-1
@@ -218,6 +218,14 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAccountingEnabled() {
|
public boolean isAccountingEnabled() {
|
||||||
return AccountingRuleType.NONE.getValue().equals(this.accountingType);
|
return !AccountingRuleType.NONE.getValue().equals(this.accountingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCashBasedAccountingEnabled(){
|
||||||
|
return AccountingRuleType.CASH_BASED.getValue().equals(this.accountingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccrualBasedAccountingEnabled(){
|
||||||
|
return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user