diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc index a7b13b941..6deed6fab 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc @@ -524,17 +524,6 @@ "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, "name": "multiDisburseLoan", @@ -637,6 +626,14 @@ "bigdecimal" ] }, + { + "default": null, + "name": "product", + "type": [ + "null", + "org.apache.fineract.avro.loan.v1.LoanProductDataV1" + ] + }, { "default": null, "name": "inArrears", diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDeletedV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDeletedV1.avsc new file mode 100644 index 000000000..1ca0d65a5 --- /dev/null +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDeletedV1.avsc @@ -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" + ] + } + ] +} diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionWithLoanV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionWithLoanV1.avsc new file mode 100644 index 000000000..0ccb5da3c --- /dev/null +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionWithLoanV1.avsc @@ -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" + ] + } + ] +} diff --git a/fineract-doc/src/docs/en/chapters/architecture/index.adoc b/fineract-doc/src/docs/en/chapters/architecture/index.adoc index 0bcafa564..54f54a2dc 100644 --- a/fineract-doc/src/docs/en/chapters/architecture/index.adoc +++ b/fineract-doc/src/docs/en/chapters/architecture/index.adoc @@ -29,3 +29,5 @@ include::technology.adoc[leveloffset=+1] include::modules.adoc[leveloffset=+1] include::business-date.adoc[leveloffset=+1] + +include::reliable-event-framework.adoc[leveloffset=+1] diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanRescheduledDueCalendarChangeBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanRescheduledDueCalendarChangeBusinessEvent.java new file mode 100644 index 000000000..bdf9db68d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanRescheduledDueCalendarChangeBusinessEvent.java @@ -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"; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanRescheduledDueHolidayBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanRescheduledDueHolidayBusinessEvent.java new file mode 100644 index 000000000..c2a327f4e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanRescheduledDueHolidayBusinessEvent.java @@ -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"; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanScheduleVariationsAddedBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanScheduleVariationsAddedBusinessEvent.java new file mode 100644 index 000000000..cf68c7156 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanScheduleVariationsAddedBusinessEvent.java @@ -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"; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanScheduleVariationsDeletedBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanScheduleVariationsDeletedBusinessEvent.java new file mode 100644 index 000000000..6c7a9bb84 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanScheduleVariationsDeletedBusinessEvent.java @@ -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"; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanStatusChangedBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanStatusChangedBusinessEvent.java new file mode 100644 index 000000000..0ca6cf7df --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanStatusChangedBusinessEvent.java @@ -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"; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanUpdateDisbursementDataBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanUpdateDisbursementDataBusinessEvent.java new file mode 100644 index 000000000..6a9c4e09d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanUpdateDisbursementDataBusinessEvent.java @@ -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"; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventService.java index 0f79a0895..3b56a0314 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventService.java @@ -19,6 +19,8 @@ package org.apache.fineract.infrastructure.event.external.service; import java.io.IOException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; import org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository; @@ -38,6 +40,8 @@ public class ExternalEventService { private final ExternalEventIdempotencyKeyGenerator idempotencyKeyGenerator; private final BusinessEventSerializerFactory serializerFactory; + private EntityManager entityManager; + public void postEvent(BusinessEvent event) { if (event == null) { throw new IllegalArgumentException("event cannot be null"); @@ -48,6 +52,7 @@ public class ExternalEventService { try { BusinessEventSerializer serializer = serializerFactory.create(event); String schema = serializer.getSupportedSchema().getName(); + flushChangesBeforeSerialization(); byte[] data = serializer.serialize(event); 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; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeDeletedBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeDeletedBusinessEventSerializer.java new file mode 100644 index 000000000..1eafe0a64 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeDeletedBusinessEventSerializer.java @@ -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 boolean canSerialize(BusinessEvent event) { + return event instanceof LoanDeleteChargeBusinessEvent; + } + + @Override + public byte[] serialize(BusinessEvent 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 getSupportedSchema() { + return LoanChargeDeletedV1.class; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java index 8b4522fe7..fb8722bb3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java @@ -22,12 +22,16 @@ import java.io.IOException; import java.nio.ByteBuffer; import lombok.RequiredArgsConstructor; 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.LoanTransactionWithLoanV1; 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.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.serializer.BusinessEventSerializer; 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.service.LoanReadPlatformService; import org.springframework.stereotype.Component; @@ -37,7 +41,8 @@ import org.springframework.stereotype.Component; public class LoanTransactionBusinessEventSerializer implements BusinessEventSerializer { private final LoanReadPlatformService service; - private final LoanTransactionDataMapper mapper; + private final LoanAccountDataMapper loanMapper; + private final LoanTransactionDataMapper loanTransactionMapper; private final ByteBufferConverter byteBufferConverter; @Override @@ -48,8 +53,13 @@ public class LoanTransactionBusinessEventSerializer implements BusinessEventSeri @Override public byte[] serialize(BusinessEvent rawEvent) throws IOException { LoanTransactionBusinessEvent event = (LoanTransactionBusinessEvent) rawEvent; - LoanTransactionData data = service.retrieveLoanTransaction(event.get().getLoan().getId(), event.get().getId()); - LoanTransactionDataV1 avroDto = mapper.map(data); + Long loanId = event.get().getLoan().getId(); + 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(); return byteBufferConverter.convert(buffer); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java index 977ca5c93..8f1219397 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java @@ -18,13 +18,22 @@ */ package org.apache.fineract.portfolio.loanaccount.domain; +import java.util.Arrays; import java.util.List; 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 public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine { private final List allowedLoanStatuses; + private final BusinessEventNotifierService businessEventNotifierService; + + public DefaultLoanLifecycleStateMachine(LoanStatus[] allowedLoanStatuses, BusinessEventNotifierService businessEventNotifierService) { + this(Arrays.asList(allowedLoanStatuses), businessEventNotifierService); + } @Override 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) { LoanStatus newState = getNextState(loanEvent, loan); 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) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index fb71c7a72..dfd42a124 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -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.loanschedule.data.LoanScheduleDTO; 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.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; 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.InterestCalculationPeriodMethod; 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 actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail) { - this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, this); - final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); - actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); - this.disbursedBy = currentUser; updateLoanScheduleDependentDerivedFields(); @@ -2446,7 +2440,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { 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 } - 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 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) { if (!allowTransactionsOnNonWorkingDay) { if (!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 44a332add..b0a71bce6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.domain; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -425,8 +424,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { - final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); - return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); + return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService); } private void checkClientOrGroupActive(final Loan loan) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansConfig.java index 70036a097..20bc2c91e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansConfig.java @@ -18,11 +18,7 @@ */ 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.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.Step; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; @@ -40,28 +36,15 @@ public class ApplyHolidaysToLoansConfig { @Autowired private StepBuilderFactory steps; - @Autowired - private ConfigurationDomainService configurationDomainService; - @Autowired - private HolidayRepositoryWrapper holidayRepository; - @Autowired - private LoanRepositoryWrapper loanRepositoryWrapper; - @Autowired - private LoanUtilService loanUtilService; @Bean - protected Step applyHolidaysToLoansStep() { - return steps.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).tasklet(applyHolidaysToLoansTasklet()).build(); + protected Step applyHolidaysToLoansStep(ApplyHolidaysToLoansTasklet applyHolidaysToLoansTasklet) { + return steps.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).tasklet(applyHolidaysToLoansTasklet).build(); } @Bean - public Job applyHolidaysToLoansJob() { - return jobs.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).start(applyHolidaysToLoansStep()).incrementer(new RunIdIncrementer()) - .build(); - } - - @Bean - public ApplyHolidaysToLoansTasklet applyHolidaysToLoansTasklet() { - return new ApplyHolidaysToLoansTasklet(configurationDomainService, holidayRepository, loanRepositoryWrapper, loanUtilService); + public Job applyHolidaysToLoansJob(ApplyHolidaysToLoansTasklet applyHolidaysToLoansTasklet) { + return jobs.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).start(applyHolidaysToLoansStep(applyHolidaysToLoansTasklet)) + .incrementer(new RunIdIncrementer()).build(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java index 6cccc130c..a8fbf76e4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.jobs.applyholidaystoloans; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -26,26 +27,35 @@ import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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.HolidayRepositoryWrapper; 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.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; 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.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.stereotype.Component; @Slf4j @RequiredArgsConstructor +@Component public class ApplyHolidaysToLoansTasklet implements Tasklet { private final ConfigurationDomainService configurationDomainService; private final HolidayRepositoryWrapper holidayRepository; private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanUtilService loanUtilService; + private final BusinessEventNotifierService businessEventNotifierService; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { @@ -72,7 +82,7 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet { loans.addAll(loanRepositoryWrapper.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses)); for (final Loan loan : loans) { - loan.applyHolidayToRepaymentScheduleDates(holiday, loanUtilService); + applyHolidayToRepaymentScheduleDates(loan, holiday); } loanRepositoryWrapper.save(loans); holiday.processed(); @@ -80,4 +90,80 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet { holidayRepository.save(holidays); 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 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; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java index 97eb2119b..2282c42ae 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java @@ -23,10 +23,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.ScheduleGeneratorDTO; 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.service.LoanAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional +@RequiredArgsConstructor public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePlatformService { private final LoanAccountDomainService loanAccountDomainService; private final LoanAssembler loanAssembler; private final LoanScheduleAssembler loanScheduleAssembler; - private final PlatformSecurityContext context; private final LoanUtilService loanUtilService; - - @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; - } + private final BusinessEventNotifierService businessEventNotifierService; @Override public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command) { - final Loan loan = this.loanAssembler.assembleFrom(loanId); + final Loan loan = loanAssembler.assembleFrom(loanId); Map loanTermVariations = new HashMap<>(); for (LoanTermVariations termVariations : loan.getLoanTermVariations()) { 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 changes = new HashMap<>(); List newVariationsData = new ArrayList<>(); List modifiedVariations = loan.getLoanTermVariations(); @@ -83,6 +75,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl changes.put("removedVariations", loanTermVariations.keySet()); } changes.put("loanTermVariations", newVariationsData); + businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsAddedBusinessEvent(loan)); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withLoanId(loanId) // @@ -92,7 +85,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl @Override public CommandProcessingResult deleteLoanScheduleVariations(final Long loanId) { - final Loan loan = this.loanAssembler.assembleFrom(loanId); + final Loan loan = loanAssembler.assembleFrom(loanId); List variations = loan.getLoanTermVariations(); List deletedVariations = new ArrayList<>(variations.size()); for (LoanTermVariations loanTermVariations : variations) { @@ -102,9 +95,10 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl changes.put("removedEntityIds", deletedVariations); loan.getLoanTermVariations().clear(); final LocalDate recalculateFrom = null; - ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); + ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO); - this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan); + loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan); + businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsDeletedBusinessEvent(loan)); return new CommandProcessingResultBuilder() // .withLoanId(loanId) // .with(changes) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index f8ce32daa..5b4f4f31e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -25,13 +25,14 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.temporal.ChronoField; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.PersistenceException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; 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.exception.ClientNotActiveException; 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.common.domain.PeriodFrequencyType; 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.LoanCharge; 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.LoanLifecycleStateMachine; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; 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.service.GSIMReadPlatformService; 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.orm.jpa.JpaSystemException; import org.springframework.stereotype.Service; @@ -154,10 +149,10 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @Service +@Slf4j +@RequiredArgsConstructor public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements LoanApplicationWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(LoanApplicationWritePlatformServiceJpaRepositoryImpl.class); - private final PlatformSecurityContext context; private final FromJsonHelper fromJsonHelper; private final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator; @@ -181,7 +176,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final SavingsAccountAssembler savingsAccountAssembler; private final AccountAssociationsRepository accountAssociationsRepository; private final LoanReadPlatformService loanReadPlatformService; - private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; private final BusinessEventNotifierService businessEventNotifierService; private final ConfigurationDomainService configurationDomainService; @@ -190,7 +184,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final CalendarReadPlatformService calendarReadPlatformService; private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService; private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository; - private final FineractEntityToEntityMappingRepository repository; + private final FineractEntityToEntityMappingRepository entityMappingRepository; private final FineractEntityRelationRepository fineractEntityRelationRepository; private final LoanProductReadPlatformService loanProductReadPlatformService; @@ -199,84 +193,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final GLIMAccountInfoRepository glimRepository; private final LoanRepository loanRepository; 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() { - final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); - return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); + return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService); } @Transactional @@ -1287,7 +1206,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) { - LOG.error("Error occured.", dve); + log.error("Error occured.", dve); } @Transactional @@ -1775,8 +1694,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa if (restrictToUserOfficeProperty.isEnabled()) { FineractEntityRelation fineractEntityRelation = fineractEntityRelationRepository .findOneByCodeName(FineractEntityAccessType.OFFICE_ACCESS_TO_LOAN_PRODUCTS.toStr()); - FineractEntityToEntityMapping officeToLoanProductMappingList = this.repository.findListByProductId(fineractEntityRelation, - productId, officeId); + FineractEntityToEntityMapping officeToLoanProductMappingList = this.entityMappingRepository + .findListByProductId(fineractEntityRelation, productId, officeId); if (officeToLoanProductMappingList == null) { throw new NotOfficeSpecificProductException(productId, officeId); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java index c57433d22..1b2b9d46c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java @@ -22,11 +22,11 @@ import com.google.gson.JsonElement; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.codes.domain.CodeValue; 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.data.EnumOptionData; 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.HolidayRepository; 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.rate.domain.Rate; import org.apache.fineract.portfolio.rate.service.RateAssembler; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class LoanAssembler { private final FromJsonHelper fromApiJsonHelper; @@ -112,38 +113,7 @@ public class LoanAssembler { private final WorkingDaysRepositoryWrapper workingDaysRepository; private final LoanUtilService loanUtilService; private final RateAssembler rateAssembler; - - @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; - } + private final BusinessEventNotifierService businessEventNotifierService; public Loan assembleFrom(final Long accountId) { final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true); @@ -354,8 +324,7 @@ public class LoanAssembler { } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { - final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); - return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); + return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService); } public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 94e9f4467..567f88746 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -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.LoanRejectTransferBusinessEvent; 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.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.charge.LoanAddChargeBusinessEvent; 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.LoanWaiveChargeBusinessEvent; 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.LoanWaiveInterestBusinessEvent; 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 LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { - final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); - return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); + return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), businessEventNotifierService); } @Transactional @@ -795,8 +796,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public CommandProcessingResult undoGLIMLoanDisbursal(final Long loanId, final JsonCommand command) { - // GroupLoanIndividualMonitoringAccount - // glimAccount=glimRepository.findOne(loanId); final Long parentLoanId = loanId; GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(parentLoanId).orElseThrow(); List 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; } @@ -2196,7 +2195,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .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 String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); @@ -2211,7 +2210,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf 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 String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); @@ -2518,6 +2517,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } saveLoanWithDataIntegrityViolationChecks(loan); + businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueCalendarChangeBusinessEvent(loan)); } } } @@ -2971,7 +2971,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.loanProduct().maxTrancheCount(), loan.getDisbursementDetails().size()); } 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); 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 @Override public void recalculateInterest(final long loanId) { diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java index b384fcce9..32b998ca1 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventServiceTest.java @@ -29,6 +29,7 @@ import java.time.LocalDate; import java.time.ZoneId; import java.util.HashMap; import java.util.Map; +import javax.persistence.EntityManager; import org.apache.fineract.avro.loan.v1.LoanAccountDataV1; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; 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.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -57,12 +57,15 @@ class ExternalEventServiceTest { private ExternalEventIdempotencyKeyGenerator idempotencyKeyGenerator; @Mock private BusinessEventSerializerFactory serializerFactory; + @Mock + private EntityManager entityManager; - @InjectMocks private ExternalEventService underTest; @BeforeEach public void setUp() { + underTest = new ExternalEventService(repository, idempotencyKeyGenerator, serializerFactory); + underTest.setEntityManager(entityManager); FineractPlatformTenant tenant = new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null); ThreadLocalContextUtil.setTenant(tenant); ThreadLocalContextUtil