FINERACT-1734: New business events and refactorings

This commit is contained in:
Arnold Galovics
2022-09-11 16:56:03 +02:00
committed by Arnold Gálovics
parent d06c3655af
commit 130d9c0c3d
23 changed files with 505 additions and 356 deletions
@@ -524,17 +524,6 @@
"org.apache.fineract.avro.portfolio.v1.PortfolioAccountDataV1" "org.apache.fineract.avro.portfolio.v1.PortfolioAccountDataV1"
] ]
}, },
{
"default": null,
"name": "accountLinkingOptions",
"type": [
"null",
{
"type": "array",
"items": "org.apache.fineract.avro.portfolio.v1.PortfolioAccountDataV1"
}
]
},
{ {
"default": null, "default": null,
"name": "multiDisburseLoan", "name": "multiDisburseLoan",
@@ -637,6 +626,14 @@
"bigdecimal" "bigdecimal"
] ]
}, },
{
"default": null,
"name": "product",
"type": [
"null",
"org.apache.fineract.avro.loan.v1.LoanProductDataV1"
]
},
{ {
"default": null, "default": null,
"name": "inArrears", "name": "inArrears",
@@ -0,0 +1,23 @@
{
"name": "LoanChargeDeletedV1",
"namespace": "org.apache.fineract.avro.loan.v1",
"type": "record",
"fields": [
{
"default": null,
"name": "id",
"type": [
"null",
"long"
]
},
{
"default": null,
"name": "chargeId",
"type": [
"null",
"long"
]
}
]
}
@@ -0,0 +1,23 @@
{
"name": "LoanTransactionWithLoanV1",
"namespace": "org.apache.fineract.avro.loan.v1",
"type": "record",
"fields": [
{
"default": null,
"name": "loan",
"type": [
"null",
"org.apache.fineract.avro.loan.v1.LoanAccountDataV1"
]
},
{
"default": null,
"name": "loanTransaction",
"type": [
"null",
"org.apache.fineract.avro.loan.v1.LoanTransactionDataV1"
]
}
]
}
@@ -29,3 +29,5 @@ include::technology.adoc[leveloffset=+1]
include::modules.adoc[leveloffset=+1] include::modules.adoc[leveloffset=+1]
include::business-date.adoc[leveloffset=+1] include::business-date.adoc[leveloffset=+1]
include::reliable-event-framework.adoc[leveloffset=+1]
@@ -0,0 +1,33 @@
/**
* 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.
*/
package org.apache.fineract.infrastructure.event.business.domain.loan;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public class LoanRescheduledDueCalendarChangeBusinessEvent extends LoanBusinessEvent {
public LoanRescheduledDueCalendarChangeBusinessEvent(Loan value) {
super(value);
}
@Override
public String getType() {
return "LoanRescheduledDueCalendarChangeBusinessEvent";
}
}
@@ -0,0 +1,33 @@
/**
* 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.
*/
package org.apache.fineract.infrastructure.event.business.domain.loan;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public class LoanRescheduledDueHolidayBusinessEvent extends LoanBusinessEvent {
public LoanRescheduledDueHolidayBusinessEvent(Loan value) {
super(value);
}
@Override
public String getType() {
return "LoanRescheduledDueHolidayBusinessEvent";
}
}
@@ -0,0 +1,33 @@
/**
* 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.
*/
package org.apache.fineract.infrastructure.event.business.domain.loan;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public class LoanScheduleVariationsAddedBusinessEvent extends LoanBusinessEvent {
public LoanScheduleVariationsAddedBusinessEvent(Loan value) {
super(value);
}
@Override
public String getType() {
return "LoanScheduleVariationsAddedBusinessEvent";
}
}
@@ -0,0 +1,33 @@
/**
* 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.
*/
package org.apache.fineract.infrastructure.event.business.domain.loan;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public class LoanScheduleVariationsDeletedBusinessEvent extends LoanBusinessEvent {
public LoanScheduleVariationsDeletedBusinessEvent(Loan value) {
super(value);
}
@Override
public String getType() {
return "LoanScheduleVariationsDeletedBusinessEvent";
}
}
@@ -0,0 +1,33 @@
/**
* 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.
*/
package org.apache.fineract.infrastructure.event.business.domain.loan;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public class LoanStatusChangedBusinessEvent extends LoanBusinessEvent {
public LoanStatusChangedBusinessEvent(Loan value) {
super(value);
}
@Override
public String getType() {
return "LoanStatusChangedBusinessEvent";
}
}
@@ -0,0 +1,33 @@
/**
* 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.
*/
package org.apache.fineract.infrastructure.event.business.domain.loan;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
public class LoanUpdateDisbursementDataBusinessEvent extends LoanBusinessEvent {
public LoanUpdateDisbursementDataBusinessEvent(Loan value) {
super(value);
}
@Override
public String getType() {
return "LoanUpdateDisbursementDataBusinessEvent";
}
}
@@ -19,6 +19,8 @@
package org.apache.fineract.infrastructure.event.external.service; package org.apache.fineract.infrastructure.event.external.service;
import java.io.IOException; import java.io.IOException;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository; import org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository;
@@ -38,6 +40,8 @@ public class ExternalEventService {
private final ExternalEventIdempotencyKeyGenerator idempotencyKeyGenerator; private final ExternalEventIdempotencyKeyGenerator idempotencyKeyGenerator;
private final BusinessEventSerializerFactory serializerFactory; private final BusinessEventSerializerFactory serializerFactory;
private EntityManager entityManager;
public <T> void postEvent(BusinessEvent<T> event) { public <T> void postEvent(BusinessEvent<T> event) {
if (event == null) { if (event == null) {
throw new IllegalArgumentException("event cannot be null"); throw new IllegalArgumentException("event cannot be null");
@@ -48,6 +52,7 @@ public class ExternalEventService {
try { try {
BusinessEventSerializer serializer = serializerFactory.create(event); BusinessEventSerializer serializer = serializerFactory.create(event);
String schema = serializer.getSupportedSchema().getName(); String schema = serializer.getSupportedSchema().getName();
flushChangesBeforeSerialization();
byte[] data = serializer.serialize(event); byte[] data = serializer.serialize(event);
ExternalEvent externalEvent = new ExternalEvent(eventType, schema, data, idempotencyKey); ExternalEvent externalEvent = new ExternalEvent(eventType, schema, data, idempotencyKey);
@@ -57,4 +62,13 @@ public class ExternalEventService {
} }
} }
private void flushChangesBeforeSerialization() {
entityManager.flush();
}
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
} }
@@ -0,0 +1,62 @@
/**
* 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.
*/
package org.apache.fineract.infrastructure.event.external.service.serialization.serializer.loan;
import java.io.IOException;
import java.nio.ByteBuffer;
import lombok.RequiredArgsConstructor;
import org.apache.avro.generic.GenericContainer;
import org.apache.fineract.avro.loan.v1.LoanChargeDeletedV1;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanDeleteChargeBusinessEvent;
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
import org.apache.fineract.infrastructure.event.external.service.support.ByteBufferConverter;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class LoanChargeDeletedBusinessEventSerializer implements BusinessEventSerializer {
private final ByteBufferConverter byteBufferConverter;
@Override
public <T> boolean canSerialize(BusinessEvent<T> event) {
return event instanceof LoanDeleteChargeBusinessEvent;
}
@Override
public <T> byte[] serialize(BusinessEvent<T> rawEvent) throws IOException {
LoanDeleteChargeBusinessEvent event = (LoanDeleteChargeBusinessEvent) rawEvent;
LoanCharge loanCharge = event.get();
Long id = loanCharge.getId();
Long chargeId = loanCharge.getCharge().getId();
LoanChargeDeletedV1 avroDto = new LoanChargeDeletedV1(id, chargeId);
ByteBuffer buffer = avroDto.toByteBuffer();
return byteBufferConverter.convert(buffer);
}
@Override
public Class<? extends GenericContainer> getSupportedSchema() {
return LoanChargeDeletedV1.class;
}
}
@@ -22,12 +22,16 @@ import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.avro.generic.GenericContainer; import org.apache.avro.generic.GenericContainer;
import org.apache.fineract.avro.loan.v1.LoanAccountDataV1;
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1; import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
import org.apache.fineract.avro.loan.v1.LoanTransactionWithLoanV1;
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanAccountDataMapper;
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanTransactionDataMapper; import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanTransactionDataMapper;
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer; import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
import org.apache.fineract.infrastructure.event.external.service.support.ByteBufferConverter; import org.apache.fineract.infrastructure.event.external.service.support.ByteBufferConverter;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -37,7 +41,8 @@ import org.springframework.stereotype.Component;
public class LoanTransactionBusinessEventSerializer implements BusinessEventSerializer { public class LoanTransactionBusinessEventSerializer implements BusinessEventSerializer {
private final LoanReadPlatformService service; private final LoanReadPlatformService service;
private final LoanTransactionDataMapper mapper; private final LoanAccountDataMapper loanMapper;
private final LoanTransactionDataMapper loanTransactionMapper;
private final ByteBufferConverter byteBufferConverter; private final ByteBufferConverter byteBufferConverter;
@Override @Override
@@ -48,8 +53,13 @@ public class LoanTransactionBusinessEventSerializer implements BusinessEventSeri
@Override @Override
public <T> byte[] serialize(BusinessEvent<T> rawEvent) throws IOException { public <T> byte[] serialize(BusinessEvent<T> rawEvent) throws IOException {
LoanTransactionBusinessEvent event = (LoanTransactionBusinessEvent) rawEvent; LoanTransactionBusinessEvent event = (LoanTransactionBusinessEvent) rawEvent;
LoanTransactionData data = service.retrieveLoanTransaction(event.get().getLoan().getId(), event.get().getId()); Long loanId = event.get().getLoan().getId();
LoanTransactionDataV1 avroDto = mapper.map(data); LoanAccountData loanData = service.retrieveOne(loanId);
LoanAccountDataV1 loanAvroDto = loanMapper.map(loanData);
Long loanTransactionId = event.get().getId();
LoanTransactionData transactionData = service.retrieveLoanTransaction(loanId, loanTransactionId);
LoanTransactionDataV1 loanTransactionAvroDto = loanTransactionMapper.map(transactionData);
LoanTransactionWithLoanV1 avroDto = new LoanTransactionWithLoanV1(loanAvroDto, loanTransactionAvroDto);
ByteBuffer buffer = avroDto.toByteBuffer(); ByteBuffer buffer = avroDto.toByteBuffer();
return byteBufferConverter.convert(buffer); return byteBufferConverter.convert(buffer);
} }
@@ -18,13 +18,22 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.domain; package org.apache.fineract.portfolio.loanaccount.domain;
import java.util.Arrays;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
// TODO: introduce tests for the state machine
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine { public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine {
private final List<LoanStatus> allowedLoanStatuses; private final List<LoanStatus> allowedLoanStatuses;
private final BusinessEventNotifierService businessEventNotifierService;
public DefaultLoanLifecycleStateMachine(LoanStatus[] allowedLoanStatuses, BusinessEventNotifierService businessEventNotifierService) {
this(Arrays.asList(allowedLoanStatuses), businessEventNotifierService);
}
@Override @Override
public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) { public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) {
@@ -35,6 +44,15 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
public void transition(final LoanEvent loanEvent, final Loan loan) { public void transition(final LoanEvent loanEvent, final Loan loan) {
LoanStatus newState = getNextState(loanEvent, loan); LoanStatus newState = getNextState(loanEvent, loan);
loan.setLoanStatus(newState.getValue()); loan.setLoanStatus(newState.getValue());
if (isNotLoanCreation(loanEvent)) {
// in case of Loan creation, a LoanCreatedBusinessEvent is also raised, no need to send a status change
businessEventNotifierService.notifyPostBusinessEvent(new LoanStatusChangedBusinessEvent(loan));
}
}
private boolean isNotLoanCreation(LoanEvent loanEvent) {
return !LoanEvent.LOAN_CREATED.equals(loanEvent);
} }
private LoanStatus getNextState(LoanEvent loanEvent, Loan loan) { private LoanStatus getNextState(LoanEvent loanEvent, Loan loan) {
@@ -122,12 +122,10 @@ import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementData
import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException; import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
@@ -2406,12 +2404,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges, public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail) { final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail) {
this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, this);
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
this.disbursedBy = currentUser; this.disbursedBy = currentUser;
updateLoanScheduleDependentDerivedFields(); updateLoanScheduleDependentDerivedFields();
@@ -2446,7 +2440,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
addLoanTransaction(interestAppliedTransaction); addLoanTransaction(interestAppliedTransaction);
} }
return reprocessTransactionForDisbursement(); ChangedTransactionDetail result = reprocessTransactionForDisbursement();
this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, this);
actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
return result;
} }
@@ -4597,82 +4594,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
// startDate // startDate
} }
public void applyHolidayToRepaymentScheduleDates(final Holiday holiday, final LoanUtilService loanUtilService) {
final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(this, holiday.getFromDateLocalDate());
final LoanApplicationTerms loanApplicationTerms = this.constructLoanApplicationTerms(scheduleGeneratorDTO);
// LocalDate rescheduleFromDate = holiday.getFromDateLocalDate();
LocalDate adjustedRescheduleToDate = null;
if (holiday.getReScheduleType().isResheduleToNextRepaymentDate()) {
final LocalDate rescheduleToDate = holiday.getToDateLocalDate();
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.getRepaymentScheduleInstallments()) {
if (rescheduleToDate.isEqual(loanRepaymentScheduleInstallment.getDueDate())) {
adjustedRescheduleToDate = rescheduleToDate;
break;
} else {
// Account for a Bimonthly Loan Product
// if (loanApplicationTerms.getRepaymentPeriodFrequencyType().isSemiMonthly()) {
// if (rescheduleToDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())
// && rescheduleToDate.isBefore(loanRepaymentScheduleInstallment.getDueDate().plusDays(15))) {
// adjustedRescheduleToDate = loanRepaymentScheduleInstallment.getDueDate();
// break;
// }
// } else {
// // Once Bimonthly product implemented, Replace the `Standard Monthly Loan Holiday check` below
// HERE
// if (rescheduleToDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())
// && rescheduleToDate.isBefore(loanRepaymentScheduleInstallment.getDueDate().plusDays(30))) {
// adjustedRescheduleToDate = loanRepaymentScheduleInstallment.getDueDate();
// break;
// }
// }
// Standard Monthly Loan Holiday check
if (rescheduleToDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())
&& rescheduleToDate.isBefore(loanRepaymentScheduleInstallment.getDueDate().plusDays(30))) {
adjustedRescheduleToDate = loanRepaymentScheduleInstallment.getDueDate();
break;
}
}
}
} else {
adjustedRescheduleToDate = holiday.getRepaymentsRescheduledToLocalDate();
}
// first repayment's from date is same as disbursement date.
LocalDate tmpFromDate = getDisbursementDate();
// Loop through all loanRepayments
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
if (adjustedRescheduleToDate == null) {
break;
}
final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
// update from date if it's not same as previous installament's due
// date.
if (!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)) {
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
}
if (oldDueDate.equals(holiday.getFromDateLocalDate()) || oldDueDate.isAfter(holiday.getFromDateLocalDate())) {
// FIXME: AA do we need to apply non-working days.
// Assuming holiday's repayment reschedule to date cannot be
// created on a non-working day.
adjustedRescheduleToDate = scheduledDateGenerator.generateNextRepaymentDate(adjustedRescheduleToDate, loanApplicationTerms,
false);
loanRepaymentScheduleInstallment.updateDueDate(adjustedRescheduleToDate);
}
tmpFromDate = loanRepaymentScheduleInstallment.getDueDate();
}
}
private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
if (!allowTransactionsOnNonWorkingDay) { if (!allowTransactionsOnNonWorkingDay) {
if (!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())) { if (!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())) {
@@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -425,8 +424,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService);
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
} }
private void checkClientOrGroupActive(final Loan loan) { private void checkClientOrGroupActive(final Loan loan) {
@@ -18,11 +18,7 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.jobs.applyholidaystoloans; package org.apache.fineract.portfolio.loanaccount.jobs.applyholidaystoloans;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.springframework.batch.core.Job; import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step; import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
@@ -40,28 +36,15 @@ public class ApplyHolidaysToLoansConfig {
@Autowired @Autowired
private StepBuilderFactory steps; private StepBuilderFactory steps;
@Autowired
private ConfigurationDomainService configurationDomainService;
@Autowired
private HolidayRepositoryWrapper holidayRepository;
@Autowired
private LoanRepositoryWrapper loanRepositoryWrapper;
@Autowired
private LoanUtilService loanUtilService;
@Bean @Bean
protected Step applyHolidaysToLoansStep() { protected Step applyHolidaysToLoansStep(ApplyHolidaysToLoansTasklet applyHolidaysToLoansTasklet) {
return steps.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).tasklet(applyHolidaysToLoansTasklet()).build(); return steps.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).tasklet(applyHolidaysToLoansTasklet).build();
} }
@Bean @Bean
public Job applyHolidaysToLoansJob() { public Job applyHolidaysToLoansJob(ApplyHolidaysToLoansTasklet applyHolidaysToLoansTasklet) {
return jobs.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).start(applyHolidaysToLoansStep()).incrementer(new RunIdIncrementer()) return jobs.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).start(applyHolidaysToLoansStep(applyHolidaysToLoansTasklet))
.build(); .incrementer(new RunIdIncrementer()).build();
}
@Bean
public ApplyHolidaysToLoansTasklet applyHolidaysToLoansTasklet() {
return new ApplyHolidaysToLoansTasklet(configurationDomainService, holidayRepository, loanRepositoryWrapper, loanUtilService);
} }
} }
@@ -18,6 +18,7 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.jobs.applyholidaystoloans; package org.apache.fineract.portfolio.loanaccount.jobs.applyholidaystoloans;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@@ -26,26 +27,35 @@ import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanRescheduledDueHolidayBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper; import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus; import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@Component
public class ApplyHolidaysToLoansTasklet implements Tasklet { public class ApplyHolidaysToLoansTasklet implements Tasklet {
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final HolidayRepositoryWrapper holidayRepository; private final HolidayRepositoryWrapper holidayRepository;
private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanRepositoryWrapper loanRepositoryWrapper;
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final BusinessEventNotifierService businessEventNotifierService;
@Override @Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
@@ -72,7 +82,7 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet {
loans.addAll(loanRepositoryWrapper.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses)); loans.addAll(loanRepositoryWrapper.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses));
for (final Loan loan : loans) { for (final Loan loan : loans) {
loan.applyHolidayToRepaymentScheduleDates(holiday, loanUtilService); applyHolidayToRepaymentScheduleDates(loan, holiday);
} }
loanRepositoryWrapper.save(loans); loanRepositoryWrapper.save(loans);
holiday.processed(); holiday.processed();
@@ -80,4 +90,80 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet {
holidayRepository.save(holidays); holidayRepository.save(holidays);
return RepeatStatus.FINISHED; return RepeatStatus.FINISHED;
} }
public void applyHolidayToRepaymentScheduleDates(Loan loan, Holiday holiday) {
LocalDate adjustedRescheduleToDate = null;
if (holiday.getReScheduleType().isResheduleToNextRepaymentDate()) {
adjustedRescheduleToDate = getNextRepaymentDate(loan, holiday);
} else {
adjustedRescheduleToDate = holiday.getRepaymentsRescheduledToLocalDate();
}
if (isRepaymentScheduleAdjustmentNeeded(adjustedRescheduleToDate)) {
adjustRepaymentSchedules(loan, holiday, adjustedRescheduleToDate);
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueHolidayBusinessEvent(loan));
}
}
private boolean isRepaymentScheduleAdjustmentNeeded(LocalDate adjustedRescheduleToDate) {
return adjustedRescheduleToDate != null;
}
private void adjustRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adjustedRescheduleToDate) {
final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, holiday.getFromDateLocalDate());
final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
// first repayment's from date is same as disbursement date.
LocalDate tmpFromDate = loan.getDisbursementDate();
// Loop through all loanRepayments
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
// update from date if it's not same as previous installment's due
// date.
if (!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)) {
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
}
if (oldDueDate.equals(holiday.getFromDateLocalDate()) || oldDueDate.isAfter(holiday.getFromDateLocalDate())) {
// FIXME: AA do we need to apply non-working days.
// Assuming holiday's repayment reschedule to date cannot be
// created on a non-working day.
adjustedRescheduleToDate = scheduledDateGenerator.generateNextRepaymentDate(adjustedRescheduleToDate, loanApplicationTerms,
false);
loanRepaymentScheduleInstallment.updateDueDate(adjustedRescheduleToDate);
}
tmpFromDate = loanRepaymentScheduleInstallment.getDueDate();
}
}
private LocalDate getNextRepaymentDate(Loan loan, Holiday holiday) {
LocalDate adjustedRescheduleToDate = null;
final LocalDate rescheduleToDate = holiday.getToDateLocalDate();
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) {
if (rescheduleToDate.isEqual(loanRepaymentScheduleInstallment.getDueDate())) {
adjustedRescheduleToDate = rescheduleToDate;
break;
} else {
adjustedRescheduleToDate = doStandardMonthlyCheck(adjustedRescheduleToDate, rescheduleToDate,
loanRepaymentScheduleInstallment);
}
}
return adjustedRescheduleToDate;
}
private LocalDate doStandardMonthlyCheck(LocalDate adjustedRescheduleToDate, LocalDate rescheduleToDate,
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
// Standard Monthly Loan Holiday check
if (rescheduleToDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())
&& rescheduleToDate.isBefore(loanRepaymentScheduleInstallment.getDueDate().plusDays(30))) {
adjustedRescheduleToDate = loanRepaymentScheduleInstallment.getDueDate();
}
return adjustedRescheduleToDate;
}
} }
@@ -23,10 +23,13 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanScheduleVariationsAddedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanScheduleVariationsDeletedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -34,41 +37,30 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@Transactional @Transactional
@RequiredArgsConstructor
public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePlatformService { public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePlatformService {
private final LoanAccountDomainService loanAccountDomainService; private final LoanAccountDomainService loanAccountDomainService;
private final LoanAssembler loanAssembler; private final LoanAssembler loanAssembler;
private final LoanScheduleAssembler loanScheduleAssembler; private final LoanScheduleAssembler loanScheduleAssembler;
private final PlatformSecurityContext context;
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public LoanScheduleWritePlatformServiceImpl(final LoanAccountDomainService loanAccountDomainService,
final LoanScheduleAssembler loanScheduleAssembler, final LoanAssembler loanAssembler, final PlatformSecurityContext context,
final LoanUtilService loanUtilService) {
this.loanAccountDomainService = loanAccountDomainService;
this.loanScheduleAssembler = loanScheduleAssembler;
this.loanAssembler = loanAssembler;
this.context = context;
this.loanUtilService = loanUtilService;
}
@Override @Override
public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command) { public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command) {
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = loanAssembler.assembleFrom(loanId);
Map<Long, LoanTermVariations> loanTermVariations = new HashMap<>(); Map<Long, LoanTermVariations> loanTermVariations = new HashMap<>();
for (LoanTermVariations termVariations : loan.getLoanTermVariations()) { for (LoanTermVariations termVariations : loan.getLoanTermVariations()) {
loanTermVariations.put(termVariations.getId(), termVariations); loanTermVariations.put(termVariations.getId(), termVariations);
} }
this.loanScheduleAssembler.assempleVariableScheduleFrom(loan, command.json()); loanScheduleAssembler.assempleVariableScheduleFrom(loan, command.json());
this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan); loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan);
final Map<String, Object> changes = new HashMap<>(); final Map<String, Object> changes = new HashMap<>();
List<LoanTermVariationsData> newVariationsData = new ArrayList<>(); List<LoanTermVariationsData> newVariationsData = new ArrayList<>();
List<LoanTermVariations> modifiedVariations = loan.getLoanTermVariations(); List<LoanTermVariations> modifiedVariations = loan.getLoanTermVariations();
@@ -83,6 +75,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl
changes.put("removedVariations", loanTermVariations.keySet()); changes.put("removedVariations", loanTermVariations.keySet());
} }
changes.put("loanTermVariations", newVariationsData); changes.put("loanTermVariations", newVariationsData);
businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsAddedBusinessEvent(loan));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withLoanId(loanId) // .withLoanId(loanId) //
@@ -92,7 +85,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl
@Override @Override
public CommandProcessingResult deleteLoanScheduleVariations(final Long loanId) { public CommandProcessingResult deleteLoanScheduleVariations(final Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = loanAssembler.assembleFrom(loanId);
List<LoanTermVariations> variations = loan.getLoanTermVariations(); List<LoanTermVariations> variations = loan.getLoanTermVariations();
List<Long> deletedVariations = new ArrayList<>(variations.size()); List<Long> deletedVariations = new ArrayList<>(variations.size());
for (LoanTermVariations loanTermVariations : variations) { for (LoanTermVariations loanTermVariations : variations) {
@@ -102,9 +95,10 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl
changes.put("removedEntityIds", deletedVariations); changes.put("removedEntityIds", deletedVariations);
loan.getLoanTermVariations().clear(); loan.getLoanTermVariations().clear();
final LocalDate recalculateFrom = null; final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan); loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan);
businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsDeletedBusinessEvent(loan));
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withLoanId(loanId) // .withLoanId(loanId) //
.with(changes) // .with(changes) //
@@ -25,13 +25,14 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
@@ -87,7 +88,6 @@ import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement; import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepository;
import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler; import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.fund.domain.Fund;
@@ -104,10 +104,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonit
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagementRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
@@ -144,9 +142,6 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService; import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -154,10 +149,10 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@Service @Service
@Slf4j
@RequiredArgsConstructor
public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements LoanApplicationWritePlatformService { public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements LoanApplicationWritePlatformService {
private static final Logger LOG = LoggerFactory.getLogger(LoanApplicationWritePlatformServiceJpaRepositoryImpl.class);
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final FromJsonHelper fromJsonHelper; private final FromJsonHelper fromJsonHelper;
private final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator; private final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator;
@@ -181,7 +176,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final SavingsAccountAssembler savingsAccountAssembler; private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository; private final AccountAssociationsRepository accountAssociationsRepository;
private final LoanReadPlatformService loanReadPlatformService; private final LoanReadPlatformService loanReadPlatformService;
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
@@ -190,7 +184,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final CalendarReadPlatformService calendarReadPlatformService; private final CalendarReadPlatformService calendarReadPlatformService;
private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService; private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService;
private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository; private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository;
private final FineractEntityToEntityMappingRepository repository; private final FineractEntityToEntityMappingRepository entityMappingRepository;
private final FineractEntityRelationRepository fineractEntityRelationRepository; private final FineractEntityRelationRepository fineractEntityRelationRepository;
private final LoanProductReadPlatformService loanProductReadPlatformService; private final LoanProductReadPlatformService loanProductReadPlatformService;
@@ -199,84 +193,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final GLIMAccountInfoRepository glimRepository; private final GLIMAccountInfoRepository glimRepository;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final GSIMReadPlatformService gsimReadPlatformService; private final GSIMReadPlatformService gsimReadPlatformService;
private final LoanCollateralManagementRepository loanCollateralManagementRepository;
private final ClientCollateralManagementRepository clientCollateralManagementRepository;
@Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator,
final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer,
final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer, final AprCalculator aprCalculator,
final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler,
final LoanCollateralAssembler loanCollateralAssembler, final LoanRepositoryWrapper loanRepositoryWrapper,
final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService,
final ClientRepositoryWrapper clientRepository, final LoanProductRepository loanProductRepository,
final AccountNumberGenerator accountNumberGenerator, final LoanSummaryWrapper loanSummaryWrapper,
final GroupRepositoryWrapper groupRepository,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository,
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanReadPlatformService loanReadPlatformService, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
final BusinessEventNotifierService businessEventNotifierService, final ConfigurationDomainService configurationDomainService,
final LoanScheduleAssembler loanScheduleAssembler, final LoanUtilService loanUtilService,
final CalendarReadPlatformService calendarReadPlatformService,
final GlobalConfigurationRepositoryWrapper globalConfigurationRepository,
final FineractEntityToEntityMappingRepository repository,
final FineractEntityRelationRepository fineractEntityRelationRepository,
final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService,
final GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService, final GLIMAccountInfoRepository glimRepository,
final LoanRepository loanRepository, final GSIMReadPlatformService gsimReadPlatformService, final RateAssembler rateAssembler,
final LoanProductReadPlatformService loanProductReadPlatformService,
final LoanCollateralManagementRepository loanCollateralManagementRepository,
final ClientCollateralManagementRepository clientCollateralManagementRepository) {
this.context = context;
this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer;
this.aprCalculator = aprCalculator;
this.loanAssembler = loanAssembler;
this.loanChargeAssembler = loanChargeAssembler;
this.loanCollateralAssembler = loanCollateralAssembler;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.noteRepository = noteRepository;
this.calculationPlatformService = calculationPlatformService;
this.clientRepository = clientRepository;
this.loanProductRepository = loanProductRepository;
this.accountNumberGenerator = accountNumberGenerator;
this.loanSummaryWrapper = loanSummaryWrapper;
this.groupRepository = groupRepository;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.calendarRepository = calendarRepository;
this.calendarInstanceRepository = calendarInstanceRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository;
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
this.loanReadPlatformService = loanReadPlatformService;
this.accountNumberFormatRepository = accountNumberFormatRepository;
this.businessEventNotifierService = businessEventNotifierService;
this.configurationDomainService = configurationDomainService;
this.loanScheduleAssembler = loanScheduleAssembler;
this.loanUtilService = loanUtilService;
this.calendarReadPlatformService = calendarReadPlatformService;
this.entityDatatableChecksWritePlatformService = entityDatatableChecksWritePlatformService;
this.globalConfigurationRepository = globalConfigurationRepository;
this.repository = repository;
this.fineractEntityRelationRepository = fineractEntityRelationRepository;
this.loanProductReadPlatformService = loanProductReadPlatformService;
this.rateAssembler = rateAssembler;
this.glimAccountInfoWritePlatformService = glimAccountInfoWritePlatformService;
this.glimRepository = glimRepository;
this.loanRepository = loanRepository;
this.gsimReadPlatformService = gsimReadPlatformService;
this.loanCollateralManagementRepository = loanCollateralManagementRepository;
this.clientCollateralManagementRepository = clientCollateralManagementRepository;
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService);
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
} }
@Transactional @Transactional
@@ -1287,7 +1206,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
} }
private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) { private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) {
LOG.error("Error occured.", dve); log.error("Error occured.", dve);
} }
@Transactional @Transactional
@@ -1775,8 +1694,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
if (restrictToUserOfficeProperty.isEnabled()) { if (restrictToUserOfficeProperty.isEnabled()) {
FineractEntityRelation fineractEntityRelation = fineractEntityRelationRepository FineractEntityRelation fineractEntityRelation = fineractEntityRelationRepository
.findOneByCodeName(FineractEntityAccessType.OFFICE_ACCESS_TO_LOAN_PRODUCTS.toStr()); .findOneByCodeName(FineractEntityAccessType.OFFICE_ACCESS_TO_LOAN_PRODUCTS.toStr());
FineractEntityToEntityMapping officeToLoanProductMappingList = this.repository.findListByProductId(fineractEntityRelation, FineractEntityToEntityMapping officeToLoanProductMappingList = this.entityMappingRepository
productId, officeId); .findListByProductId(fineractEntityRelation, productId, officeId);
if (officeToLoanProductMappingList == null) { if (officeToLoanProductMappingList == null) {
throw new NotOfficeSpecificProductException(productId, officeId); throw new NotOfficeSpecificProductException(productId, officeId);
} }
@@ -22,11 +22,11 @@ import com.google.gson.JsonElement;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
@@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository; import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType; import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
@@ -87,10 +88,10 @@ import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequired
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.rate.domain.Rate;
import org.apache.fineract.portfolio.rate.service.RateAssembler; import org.apache.fineract.portfolio.rate.service.RateAssembler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor
public class LoanAssembler { public class LoanAssembler {
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
@@ -112,38 +113,7 @@ public class LoanAssembler {
private final WorkingDaysRepositoryWrapper workingDaysRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final RateAssembler rateAssembler; private final RateAssembler rateAssembler;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanRepositoryWrapper loanRepository,
final LoanProductRepository loanProductRepository, final ClientRepositoryWrapper clientRepository,
final GroupRepository groupRepository, final FundRepository fundRepository,
final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
final StaffRepository staffRepository, final CodeValueRepositoryWrapper codeValueRepository,
final LoanScheduleAssembler loanScheduleAssembler, final LoanChargeAssembler loanChargeAssembler,
final LoanCollateralAssembler collateralAssembler, final LoanSummaryWrapper loanSummaryWrapper,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanUtilService loanUtilService, RateAssembler rateAssembler) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.loanRepository = loanRepository;
this.loanProductRepository = loanProductRepository;
this.clientRepository = clientRepository;
this.groupRepository = groupRepository;
this.fundRepository = fundRepository;
this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
this.staffRepository = staffRepository;
this.codeValueRepository = codeValueRepository;
this.loanScheduleAssembler = loanScheduleAssembler;
this.loanChargeAssembler = loanChargeAssembler;
this.collateralAssembler = collateralAssembler;
this.loanSummaryWrapper = loanSummaryWrapper;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.holidayRepository = holidayRepository;
this.configurationDomainService = configurationDomainService;
this.workingDaysRepository = workingDaysRepository;
this.loanUtilService = loanUtilService;
this.rateAssembler = rateAssembler;
}
public Loan assembleFrom(final Long accountId) { public Loan assembleFrom(final Long accountId) {
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true); final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
@@ -354,8 +324,7 @@ public class LoanAssembler {
} }
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService);
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
} }
public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) { public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) {
@@ -66,15 +66,17 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanInteres
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanReassignOfficerBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanReassignOfficerBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanRejectTransferBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanRejectTransferBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanRemoveOfficerBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanRemoveOfficerBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanRescheduledDueCalendarChangeBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoDisbursalBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoDisbursalBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoLastDisbursalBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoLastDisbursalBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUpdateDisbursementDataBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanWithdrawTransferBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanWithdrawTransferBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanAddChargeBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanAddChargeBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanDeleteChargeBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanDeleteChargeBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanUpdateChargeBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanUpdateChargeBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanWaiveChargeBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanWaiveChargeBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanWaiveChargeUndoBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanWaiveChargeUndoBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargePaymentPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeRefundBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWrittenOffPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWrittenOffPostBusinessEvent;
@@ -269,8 +271,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository; private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService);
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
} }
@Transactional @Transactional
@@ -795,8 +796,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional @Transactional
@Override @Override
public CommandProcessingResult undoGLIMLoanDisbursal(final Long loanId, final JsonCommand command) { public CommandProcessingResult undoGLIMLoanDisbursal(final Long loanId, final JsonCommand command) {
// GroupLoanIndividualMonitoringAccount
// glimAccount=glimRepository.findOne(loanId);
final Long parentLoanId = loanId; final Long parentLoanId = loanId;
GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(parentLoanId).orElseThrow(); GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(parentLoanId).orElseThrow();
List<Loan> childLoans = this.loanRepository.findByGlimId(loanId); List<Loan> childLoans = this.loanRepository.findByGlimId(loanId);
@@ -1727,7 +1726,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargePaymentPostBusinessEvent(newChargeRefundTxn)); businessEventNotifierService.notifyPostBusinessEvent(new LoanChargeRefundBusinessEvent(newChargeRefundTxn));
return result; return result;
} }
@@ -2196,7 +2195,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.withSavingsId(portfolioAccountData.getId()).build(); .withSavingsId(portfolioAccountData.getId()).build();
} }
public void disburseLoanToLoan(final Loan loan, final JsonCommand command, final BigDecimal amount) { private void disburseLoanToLoan(final Loan loan, final JsonCommand command, final BigDecimal amount) {
final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
@@ -2211,7 +2210,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.getTopupLoanDetails().setTopupAmount(amount); loan.getTopupLoanDetails().setTopupAmount(amount);
} }
public void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) { private void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) {
final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
@@ -2518,6 +2517,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueCalendarChangeBusinessEvent(loan));
} }
} }
} }
@@ -2971,7 +2971,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.loanProduct().maxTrancheCount(), loan.getDisbursementDetails().size()); loan.loanProduct().maxTrancheCount(), loan.getDisbursementDetails().size());
} }
LoanDisbursementDetails updateDetails = null; LoanDisbursementDetails updateDetails = null;
return processLoanDisbursementDetail(loan, loanId, command, updateDetails); CommandProcessingResult result = processLoanDisbursementDetail(loan, loanId, command, updateDetails);
businessEventNotifierService.notifyPostBusinessEvent(new LoanUpdateDisbursementDataBusinessEvent(loan));
return result;
} }
@@ -3040,85 +3042,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId); LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails); this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails);
return processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails); CommandProcessingResult result = processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails);
businessEventNotifierService.notifyPostBusinessEvent(new LoanUpdateDisbursementDataBusinessEvent(loan));
return result;
} }
public LoanTransaction disburseLoanAmountToSavings(final Long loanId, Long loanChargeId, final JsonCommand command,
final boolean isChargeIdIncludedInJson) {
LoanTransaction transaction = null;
this.loanEventApiJsonValidator.validateChargePaymentTransaction(command.json(), isChargeIdIncludedInJson);
if (isChargeIdIncludedInJson) {
loanChargeId = command.longValueOfParameterNamed("chargeId");
}
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
// Charges may be waived only when the loan associated with them are
// active
if (!loan.status().isActive()) {
throw new LoanChargeCannotBePayedException(LoanChargeCannotBePayedReason.LOAN_INACTIVE, loanCharge.getId());
}
// validate loan charge is not already paid or waived
if (loanCharge.isWaived()) {
throw new LoanChargeCannotBePayedException(LoanChargeCannotBePayedReason.ALREADY_WAIVED, loanCharge.getId());
} else if (loanCharge.isPaid()) {
throw new LoanChargeCannotBePayedException(LoanChargeCannotBePayedReason.ALREADY_PAID, loanCharge.getId());
}
if (!loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
throw new LoanChargeCannotBePayedException(LoanChargeCannotBePayedReason.CHARGE_NOT_ACCOUNT_TRANSFER, loanCharge.getId());
}
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
Integer loanInstallmentNumber = null;
BigDecimal amount = loanCharge.amountOutstanding();
if (loanCharge.isInstalmentFee()) {
LoanInstallmentCharge chargePerInstallment = null;
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber");
if (dueDate != null) {
chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate);
} else if (installmentNumber != null) {
chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber);
}
if (chargePerInstallment == null) {
chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
}
if (chargePerInstallment.isWaived()) {
throw new LoanChargeCannotBePayedException(LoanChargeCannotBePayedReason.ALREADY_WAIVED, loanCharge.getId());
} else if (chargePerInstallment.isPaid()) {
throw new LoanChargeCannotBePayedException(LoanChargeCannotBePayedReason.ALREADY_PAID, loanCharge.getId());
}
loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber();
amount = chargePerInstallment.getAmountOutstanding();
}
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
if (portfolioAccountData == null) {
final String errorMessage = "Charge with id:" + loanChargeId + " requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge.pay", errorMessage, loanChargeId);
}
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS,
PortfolioAccountType.LOAN, portfolioAccountData.getId(), loanId, "Loan Charge Payment", locale, fmt, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
return transaction;
}
@Transactional @Transactional
@Override @Override
public void recalculateInterest(final long loanId) { public void recalculateInterest(final long loanId) {
@@ -29,6 +29,7 @@ import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.persistence.EntityManager;
import org.apache.fineract.avro.loan.v1.LoanAccountDataV1; import org.apache.fineract.avro.loan.v1.LoanAccountDataV1;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
@@ -43,7 +44,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@@ -57,12 +57,15 @@ class ExternalEventServiceTest {
private ExternalEventIdempotencyKeyGenerator idempotencyKeyGenerator; private ExternalEventIdempotencyKeyGenerator idempotencyKeyGenerator;
@Mock @Mock
private BusinessEventSerializerFactory serializerFactory; private BusinessEventSerializerFactory serializerFactory;
@Mock
private EntityManager entityManager;
@InjectMocks
private ExternalEventService underTest; private ExternalEventService underTest;
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
underTest = new ExternalEventService(repository, idempotencyKeyGenerator, serializerFactory);
underTest.setEntityManager(entityManager);
FineractPlatformTenant tenant = new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null); FineractPlatformTenant tenant = new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null);
ThreadLocalContextUtil.setTenant(tenant); ThreadLocalContextUtil.setTenant(tenant);
ThreadLocalContextUtil ThreadLocalContextUtil