Skip to content

Commit

Permalink
Merge branch 'fiter/sumas/dev' of github.com:fiterlatam/fineract-temp…
Browse files Browse the repository at this point in the history
…late into fiter/sumas/dev
  • Loading branch information
faheem205 committed Dec 21, 2024
2 parents 07f4a25 + f8a6fae commit caa97a7
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,45 @@ public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment
}
}

// SU-527 SU-530
// This method is created to avoid creation of duplicate installments on transaction reversal.
// Instead of fineract built-in way of using the newely calculated installments, this function copies the component
// amounts
// of newely calculated installments to existing installments so that the charges remain intact. If some
// installments are removed because
// of advance payment then those are removed from the original installment list and vice versa added to the new
// installment list in case of reschedule
public void updateLoanSchedule(final LoanScheduleDTO loanSchedule) {
List<LoanRepaymentScheduleInstallment> scheduleInstallments = loanSchedule.getInstallments();
List<LoanRepaymentScheduleInstallment> removeInstallments = new ArrayList<>();
for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
// Do not touch graced or fully paid installments
if (installment.getInstallmentNumber() == 0 || installment.isObligationsMet()) {
continue;
}
LoanRepaymentScheduleInstallment scheduledInstallment = findByInstallmentNumber(scheduleInstallments,
installment.getInstallmentNumber());
if (scheduledInstallment != null) {
installment.updateComponents(scheduledInstallment, this.getCurrency());
} else {
// This can only be possible in case of big advance payment which reduces the installment count
removeInstallments.add(installment);
}
}
// Check if there are extra installments created because of loan rescheduling then add those newely created
// installments
if (this.repaymentScheduleInstallments.size() < scheduleInstallments.size()) {
int newInstallmentsCount = scheduleInstallments.size() - this.repaymentScheduleInstallments.size();
for (int i = newInstallmentsCount; i > 0; i--) {
int index = scheduleInstallments.size() - i;
addLoanRepaymentScheduleInstallment(scheduleInstallments.get(index));
}
}
if (!removeInstallments.isEmpty()) {
this.repaymentScheduleInstallments.removeAll(removeInstallments);
}
}

private LoanRepaymentScheduleInstallment findByInstallmentNumber(Collection<LoanRepaymentScheduleInstallment> installments,
Integer installmentNumber) {
for (LoanRepaymentScheduleInstallment installment : installments) {
Expand Down Expand Up @@ -6216,7 +6255,9 @@ public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleG
}
// Either the installments got recalculated or the model
if (loanSchedule.getInstallments() != null) {
updateLoanSchedule(loanSchedule.getInstallments());
// SU-527 SU-530 using newely created method to update the loan schedule
// updateLoanSchedule(loanSchedule.getInstallments());
updateLoanSchedule(loanSchedule);
} else {
updateLoanSchedule(loanSchedule.getLoanScheduleModel());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1741,4 +1741,19 @@ public BigDecimal originalInterestChargedAmount() {
public boolean isMigratedInstallment() {
return isMigratedInstallment;
}

public void updateComponents(LoanRepaymentScheduleInstallment copy, MonetaryCurrency currency) {
this.setPrincipal(copy.getPrincipal(currency).getAmount());
this.setRecalculateEMI(copy.recalculateEMI);
this.setRecalculatedInterestComponent(copy.recalculatedInterestComponent);
this.setAdvancePrincipalAmount(copy.getAdvancePrincipalAmount());
this.setPenaltyCharges(copy.getPenaltyChargesCharged(currency).getAmount());
this.setFeeChargesCharged(copy.getFeeChargesCharged(currency).getAmount());
this.setInterestCharged(copy.getInterestCharged(currency).getAmount());
if (copy.recalculatedInterestComponent) {
// This will only be true if loan is overpaid in advance. A new installment is created with only principal
// amount and all other null components
this.getInstallmentCharges().clear();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L
totalPrincipal, totalInstallments, outstandingBalance));
} else {
if (loanCharge.getChargeCalculation().isFlatHono()) {
cumulative = Money.zero(monetaryCurrency);
cumulative = cumulative.plus(Money.zero(monetaryCurrency));
} else {
cumulative = cumulative.plus(getInstallmentFee(monetaryCurrency, period, loanCharge));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,7 @@ public InsuranceIncidentData toData() {
return InsuranceIncidentData.instance(this.getId(), this.name, this.isMandatory, this.isVoluntary, code, value, null);
}

public boolean isValid() {
return this.isMandatory || this.isVoluntary;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ public enum InsuranceIncidentType {
PERMANENT_CANCELLATION_DUE_TO_MAX_AGE(9, "labels.inputs.insurance.incident.permanent.cancellation.max.age",
"Cancelación definitiva por edad máxima de permanencia"), //
DEATH_CANCELLATION(10, "labels.inputs.insurance.incident.death.cancellation", "Cancelación definitiva por fallecimiento"), //
SUSPENSION_REMOVED(11, "labels.inputs.insurance.incident.removed.suspension", "Salida de suspensión"); //
SUSPENSION_REMOVED(11, "labels.inputs.insurance.incident.removed.suspension", "Salida de suspensión"), //
DEFINITIVE_FINAL_INVALIDATION(12, "labels.inputs.insurance.incident.final.annulment",
"Anulación definitiva por invalidez del contrato"), //
DEFINITIVE_RESTRUCTURING_CANCELLATION(13, "labels.inputs.insurance.incident.definitive.restructuring.cancellation",
"Cancelación definitiva por reestructuración"); //

private final Integer value;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon
estadoCliente = ClientStatus.fromInt(loan.getClient().getStatus()).name();
}

BigDecimal waivedFees = currentInstallment.getFeeChargesWaived(loan.getCurrency()).getAmount();
BigDecimal waivedInterest = currentInstallment.getInterestWaived(loan.getCurrency()).getAmount();
BigDecimal waivedPenalties = currentInstallment.getPenaltyChargesWaived(loan.getCurrency()).getAmount();
BigDecimal totalWaived = waivedFees.add(waivedInterest).add(waivedPenalties);

if (existingLoanArchive.isPresent()) {

LoanArchiveHistory existingEntry = existingLoanArchive.get();
Expand Down Expand Up @@ -292,7 +297,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon
existingEntry.setSegurosVoluntarios(voluntaryInsuranceAmount);
existingEntry.setPeriodicidad(PeriodFrequencyType.fromInt(loan.getTermPeriodFrequencyType()).name());
existingEntry.setEmpresaReporta("INTERCREDITO");
existingEntry.setAbono(BigDecimal.ZERO);
existingEntry.setAbono(currentInstallment.getTotalPaid(loan.getCurrency()).getAmount());
existingEntry.setCondonaciones(totalWaived);
existingEntry.setActividadLaboral(actividadLaboral);
existingEntry.setNumeroDeReprogramaciones(numberReschedule);
existingEntry.setCreSaldo(creSaldo);
Expand Down Expand Up @@ -358,7 +364,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon
loanArchiveHistory.setSegurosVoluntarios(voluntaryInsuranceAmount);
loanArchiveHistory.setPeriodicidad(PeriodFrequencyType.fromInt(loan.getTermPeriodFrequencyType()).name());
loanArchiveHistory.setEmpresaReporta("INTERCREDITO");
loanArchiveHistory.setAbono(BigDecimal.ZERO);
loanArchiveHistory.setAbono(currentInstallment.getTotalPaid(loan.getCurrency()).getAmount());
loanArchiveHistory.setCondonaciones(totalWaived);
loanArchiveHistory.setActividadLaboral(actividadLaboral);
loanArchiveHistory.setNumeroDeReprogramaciones(numberReschedule);
loanArchiveHistory.setCreSaldo(creSaldo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ public void validateNewRepaymentTransaction(final String json) {
final Set<String> transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "transactionAmount", "externalId", "note",
"locale", "dateFormat", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber",
"loanId", "channelHash", "channelName", "pointOfSalesCode", "isImportedTransaction", "repaymentChannelId",
"repaymentBankId", "transactionProcessingStrategy", "clientIdNumber", "reduceInstallmentAmount"));
"repaymentBankId", "transactionProcessingStrategy", "clientIdNumber", "reduceInstallmentAmount", "honorariosAmount"));

final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
Expand All @@ -305,6 +305,9 @@ public void validateNewRepaymentTransaction(final String json) {
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();

final BigDecimal honorariosAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("honorariosAmount", element);
baseDataValidator.reset().parameter("honorariosAmount").value(honorariosAmount).ignoreIfNull().zeroOrPositiveAmount();

final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,10 @@ public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand
// the existing loan
amountToDisburse = disburseAmount.minus(loanOutstanding);
}
if (!"castigado".equalsIgnoreCase(loanToClose.claimType())) { // Ensure the loan is not in castigado
// state
createRestructuringCancellationEvent(loanToClose); // Generate the event
}

disburseLoanToLoan(loan, command, loanOutstanding, loanToClose);
}
Expand Down Expand Up @@ -1143,7 +1147,16 @@ public CommandProcessingResult makeLoanRepayment(final LoanTransactionType repay
.findFirst();
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");

BigDecimal honoAmount = command.bigDecimalValueOfParameterNamed("honorariosAmount");
if (honoAmount == null) {
honoAmount = BigDecimal.ZERO;
}

Money remainingAmount = Money.of(loan.getCurrency(), transactionAmount);
// SU-516 Transaction amount may contain hono amount as well. ReCalculate hono charge amount based on
// the actual transaction amount
remainingAmount = remainingAmount.minus(honoAmount);
Integer installmentNumber = 0;
// increment the batch id which will be used to delete the rows from db table when a transaction is
// rollbacked. The rows with highest version will be roll backed
Expand Down Expand Up @@ -1352,19 +1365,29 @@ private static void handleOverPaidException(String totalOverpaid) {
}

private void createCancellationNoveltyNews(Loan loan, LocalDate writeOffDate) {
InsuranceIncident incident = this.insuranceIncidentRepository
.findByIncidentType(InsuranceIncidentType.DEFINITIVE_FINAL_CANCELLATION);
if (incident == null || (!incident.isMandatory() && !incident.isVoluntary())) {
throw new InsuranceIncidentNotFoundException(InsuranceIncidentType.DEFINITIVE_FINAL_CANCELLATION.name());
createNoveltyNews(loan, writeOffDate, InsuranceIncidentType.DEFINITIVE_FINAL_CANCELLATION);
}

private void createAnulacionNoveltyNews(Loan loan, LocalDate writeOffDate) {
// Implementation for creating the novelty "Anulación"
createNoveltyNews(loan, writeOffDate, InsuranceIncidentType.DEFINITIVE_FINAL_INVALIDATION);
}

private void createNoveltyNews(Loan loan, LocalDate transactionDate, InsuranceIncidentType incidentType) {
// Fetch the Insurance Incident based on the provided type
InsuranceIncident incident = this.insuranceIncidentRepository.findByIncidentType(incidentType);
if (incident == null || (!incident.isValid())) {
throw new InsuranceIncidentNotFoundException(incidentType.name());
}

// Iterate through loan charges and create novelty news as needed
for (LoanCharge loanCharge : loan.getCharges()) {
if (loanCharge.getAmountOutstanding(loan.getCurrency()).isGreaterThanZero()) {
if ((incident.isMandatory() && loanCharge.isMandatoryInsurance())
|| (incident.isVoluntary() && loanCharge.isVoluntaryInsurance())) {
BigDecimal cumulative = BigDecimal.ZERO;
InsuranceIncidentNoveltyNews insuranceIncidentNoveltyNews = InsuranceIncidentNoveltyNews.instance(loan, loanCharge,
null, incident, writeOffDate, cumulative);
null, incident, transactionDate, cumulative);

this.insuranceIncidentNoveltyNewsRepository.saveAndFlush(insuranceIncidentNoveltyNews);
}
Expand Down Expand Up @@ -2509,6 +2532,10 @@ public CommandProcessingResult closeAsRescheduled(final Long loanId, final JsonC
.build();
}

private void createRestructuringCancellationEvent(Loan loan) {
createNoveltyNews(loan, DateUtils.getBusinessLocalDate(), InsuranceIncidentType.DEFINITIVE_RESTRUCTURING_CANCELLATION);
}

private void disburseLoanToLoan(final Loan loan, final JsonCommand command, final BigDecimal amount, final Loan loanToClose) {

final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
Expand Down Expand Up @@ -3680,7 +3707,8 @@ public CommandProcessingResult cancelLoan(final Long loanId, final JsonCommand c
* incident, transactionDate, cumulative);
* this.insuranceIncidentNoveltyNewsRepository.saveAndFlush(insuranceIncidentNoveltyNews); }
*/
createCancellationNoveltyNews(loan, transactionDate);
// Generate novelty "Anulación"
createAnulacionNoveltyNews(loan, transactionDate);
if (transactionDate.equals(loan.getDisbursementDate())) {
loan.setAnulado(true);
loan.setAnuladoOnDisbursementDate(true);
Expand All @@ -3689,6 +3717,8 @@ public CommandProcessingResult cancelLoan(final Long loanId, final JsonCommand c
externalId, changes, false);
final BlockingReasonSetting blockingReasonSetting = loanBlockingReasonRepository.getSingleBlockingReasonSettingByReason(
BlockingReasonSettingEnum.CREDIT_ANULADO.getDatabaseString(), BlockLevel.CREDIT.toString());
// not to mess with the record , we will just ensure it does not affect client level
blockingReasonSetting.setAffectsClientLevel(0);
loanBlockWritePlatformService.blockLoan(loan.getId(), blockingReasonSetting, "Anulado", DateUtils.getLocalDateOfTenant());
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
return commandProcessingResultBuilder.withLoanId(loanId).withEntityId(foreclosureTransaction.getId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,4 +335,6 @@
<include file="parts/20243008011009149_SU_488_update_report._archivo.xml" relativeToChangelogFile="true"/>
<include file="parts/202412161618100_SU_516_c_custom_charge_honorarios_map_MODIFY.xml" relativeToChangelogFile="true"/>
<include file="parts/20241216011009134_SU_502_add_column_migrated_loans.xml" relativeToChangelogFile="true"/>
<include file="parts/20240828220000_SU_457_insurance_incident_addition.xml" relativeToChangelogFile="true"/>
<include file="parts/20240828220000_SU_535_insurance_incident_addition.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">

<insert tableName="m_insurance_incidents">
<column name="name" value="Anulación definitiva por invalidez del contrato"/>
<column name="is_mandatory" value="true"/>
<column name="is_voluntary" value="true"/>
<column name="created_on_utc" value="NOW()"/>
<column name="created_by" value="1"/>
<column name="last_modified_by" value="1"/>
<column name="last_modified_on_utc" value="NOW()"/>
<column name="incident_type" value="12"/>
</insert>


</changeSet>



</databaseChangeLog>
Loading

0 comments on commit caa97a7

Please sign in to comment.