galovics commented on code in PR #5771:
URL: https://github.com/apache/fineract/pull/5771#discussion_r3221536544


##########
fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0036_wc_loan_period_payment_rate_change.xml:
##########
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+
+    <changeSet author="fineract" id="wcl-0036-1-create-rate-change-table">
+        <preConditions onFail="MARK_RAN">
+            <not>
+                <tableExists tableName="m_wc_loan_period_payment_rate_change"/>
+            </not>
+        </preConditions>
+        <createTable tableName="m_wc_loan_period_payment_rate_change">
+            <column autoIncrement="true" name="id" type="BIGINT">
+                <constraints nullable="false" primaryKey="true"/>
+            </column>
+            <column name="wc_loan_id" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="effective_date" type="DATE">
+                <constraints nullable="false"/>
+            </column>
+            <column name="previous_rate" type="DECIMAL(19,6)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="new_rate" type="DECIMAL(19,6)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="is_reversed" type="BOOLEAN" 
defaultValueBoolean="false">
+                <constraints nullable="false"/>
+            </column>
+            <column name="reversed_on_date" type="DATE"/>
+            <column name="created_by" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="last_modified_by" type="BIGINT">
+                <constraints nullable="false"/>
+            </column>
+            <column name="version" type="INT" defaultValueNumeric="0">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+    </changeSet>
+
+    <changeSet author="fineract" id="wcl-0036-2-audit-columns-my" 
context="mysql">
+        <addColumn tableName="m_wc_loan_period_payment_rate_change">
+            <column name="created_on_utc" type="DATETIME(6)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="last_modified_on_utc" type="DATETIME(6)">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+
+    <changeSet author="fineract" id="wcl-0036-2-audit-columns-pg" 
context="postgresql">
+        <addColumn tableName="m_wc_loan_period_payment_rate_change">
+            <column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
+                <constraints nullable="false"/>
+            </column>
+            <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME 
ZONE">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+
+    <changeSet author="fineract" id="wcl-0036-3-rate-change-fks">
+        <addForeignKeyConstraint baseColumnNames="wc_loan_id" 
baseTableName="m_wc_loan_period_payment_rate_change"
+                                 constraintName="fk_wc_rate_change_loan" 
referencedColumnNames="id"
+                                 referencedTableName="m_wc_loan"/>
+        <addForeignKeyConstraint baseColumnNames="created_by" 
baseTableName="m_wc_loan_period_payment_rate_change"
+                                 constraintName="fk_wc_rate_change_created_by" 
referencedColumnNames="id"
+                                 referencedTableName="m_appuser"/>
+        <addForeignKeyConstraint baseColumnNames="last_modified_by" 
baseTableName="m_wc_loan_period_payment_rate_change"
+                                 
constraintName="fk_wc_rate_change_last_modified_by" referencedColumnNames="id"
+                                 referencedTableName="m_appuser"/>
+    </changeSet>
+
+    <changeSet author="fineract" id="wcl-0036-4-rate-change-index">
+        <createIndex tableName="m_wc_loan_period_payment_rate_change" 
indexName="idx_wc_rate_change_loan_date">
+            <column name="wc_loan_id"/>
+            <column name="effective_date"/>
+        </createIndex>
+    </changeSet>
+
+    <changeSet author="fineract" id="wcl-0036-6-update-rate-permission">
+        <preConditions onFail="MARK_RAN">
+            <sqlCheck expectedResult="0">
+                SELECT COUNT(*) FROM m_permission WHERE code = 
'UPDATERATE_WORKINGCAPITALLOAN';
+            </sqlCheck>
+        </preConditions>
+        <insert tableName="m_permission">
+            <column name="grouping" value="portfolio"/>
+            <column name="code" value="UPDATERATE_WORKINGCAPITALLOAN"/>
+            <column name="entity_name" value="WORKINGCAPITALLOAN"/>
+            <column name="action_name" value="UPDATERATE"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+
+    <changeSet author="fineract" id="wcl-0036-7-undo-rate-change-permission">

Review Comment:
   Where's the endpoint / handler / service method for this? I can't find any 
`UNDORATECHANGE` implementation, just the permission row. Either drop this 
changeset until you actually implement undo, or include the implementation in 
this PR. Dead permissions are confusing.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResource.java:
##########
@@ -310,4 +313,102 @@ private CommandProcessingResult 
handleStateTransition(final Long loanId, final S
         return 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
     }
 
+    @PUT
+    @Path("{loanId}/discount")

Review Comment:
   Is this discount endpoint related to FINERACT-2455? Looks like a separate 
feature. Let's split it into its own PR.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanDataValidator.java:
##########
@@ -95,6 +96,9 @@ public class WorkingCapitalLoanDataValidator {
                     WorkingCapitalLoanConstants.noteParamName, 
WorkingCapitalLoanConstants.transactionDateParamName));
     private static final Set<String> 
CREDIT_BALANCE_REFUND_SUPPORTED_PARAMETERS = new 
HashSet<>(REPAYMENT_SUPPORTED_PARAMETERS);
 
+    private static final Set<String> UPDATE_RATE_SUPPORTED_PARAMETERS = new 
HashSet<>(
+            Arrays.asList("locale", 
WorkingCapitalLoanConstants.periodPaymentRateParamName, 
WorkingCapitalLoanConstants.noteParamName));

Review Comment:
   There's a `WorkingCapitalLoanConstants.localeParameterName` already.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanDataValidator.java:
##########
@@ -616,6 +620,58 @@ public void validateCreditBalanceRefund(final String json, 
final WorkingCapitalL
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
 
+    public void validateUpdatePeriodPaymentRate(final String json, final 
WorkingCapitalLoan loan) {

Review Comment:
   Tests please. No unit test covers: blank json, non-active loan, missing 
`periodPaymentRate`, negative rate, same-as-current rate, below product min, 
above product max, note too long. Same for 
`WorkingCapitalLoanWritePlatformServiceImpl.updatePeriodPaymentRate`. The feign 
tests only happy-path it.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanWritePlatformServiceImpl.java:
##########
@@ -668,6 +671,57 @@ public CommandProcessingResult creditBalanceRefund(final 
Long loanId, final Json
                 
.withClientId(loan.getClientId()).withLoanId(loanId).with(changes).build();
     }
 
+    @Override
+    @Transactional
+    public CommandProcessingResult updatePeriodPaymentRate(final Long loanId, 
final JsonCommand command) {
+        final WorkingCapitalLoan loan = this.loanRepository.findById(loanId)
+                .orElseThrow(() -> new 
WorkingCapitalLoanNotFoundException(loanId));
+        this.validator.validateUpdatePeriodPaymentRate(command.json(), loan);
+
+        final BigDecimal newRate = 
this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanConstants.periodPaymentRateParamName,
+                command.parsedJson(), new HashSet<>());
+        final BigDecimal previousRate = 
loan.getLoanProductRelatedDetails().getPeriodPaymentRate();
+
+        final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+
+        final List<WorkingCapitalLoanPeriodPaymentRateChange> activeChanges = 
this.rateChangeRepository
+                .findByWorkingCapitalLoanIdAndReversedFalse(loanId);
+        for (final WorkingCapitalLoanPeriodPaymentRateChange active : 
activeChanges) {
+            active.reverse(businessDate);
+        }
+        if (!activeChanges.isEmpty()) {
+            this.rateChangeRepository.saveAll(activeChanges);
+        }
+
+        loan.getLoanProductRelatedDetails().setPeriodPaymentRate(newRate);
+
+        final WorkingCapitalLoanPeriodPaymentRateChange rateChange = 
WorkingCapitalLoanPeriodPaymentRateChange.create(loan, businessDate,
+                previousRate, newRate);
+        this.rateChangeRepository.save(rateChange);
+
+        try {
+            
this.amortizationScheduleWriteService.regenerateAmortizationScheduleOnRateChange(loan,
 newRate);
+        } catch (IllegalStateException | IllegalArgumentException e) {

Review Comment:
   This is using exceptions as control flow. `IllegalStateException` / 
`IllegalArgumentException` are thrown by `Validate.notNull` and any number of 
unrelated places - you'll catch and mask real bugs here. Either validate 
preconditions upfront in the validator (preferred), or have the calculator 
throw a domain-specific checked exception you can react to.



##########
fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0036_wc_loan_period_payment_rate_change.xml:
##########
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+
+    <changeSet author="fineract" id="wcl-0036-1-create-rate-change-table">

Review Comment:
   The changeset IDs jump: 1, 2-my, 2-pg, 3, 4, 6, 7. What happened to 5? 
Renumber please.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanDataValidator.java:
##########
@@ -616,6 +620,58 @@ public void validateCreditBalanceRefund(final String json, 
final WorkingCapitalL
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
 
+    public void validateUpdatePeriodPaymentRate(final String json, final 
WorkingCapitalLoan loan) {
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        if (loan.getLoanStatus() != LoanStatus.ACTIVE) {
+            throw new 
PlatformApiDataValidationException("validation.msg.wc.loan.rate.change.not.allowed",

Review Comment:
   Why mix early `throw` with the `DataValidatorBuilder` pattern? Collect all 
errors via the builder and throw once at the end like the other validate 
methods in this class. Otherwise the client gets one error at a time which is 
annoying.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleReadServiceImpl.java:
##########
@@ -36,21 +37,17 @@
 @Transactional(readOnly = true)
 public class WorkingCapitalLoanAmortizationScheduleReadServiceImpl implements 
WorkingCapitalLoanAmortizationScheduleReadService {
 
-    // TODO: currency should come from loan product once WCL lifecycle is 
implemented
-    private static final MonetaryCurrency DEFAULT_CURRENCY = new 
MonetaryCurrency("USD", 2, null);
-
     private final WorkingCapitalLoanRepository loanRepository;
     private final ProjectedAmortizationScheduleRepositoryWrapper 
scheduleRepositoryWrapper;
     private final ProjectedAmortizationScheduleMapper mapper;
 
     @Override
     public ProjectedAmortizationScheduleData 
retrieveAmortizationSchedule(final Long loanId) {
-        if (!loanRepository.existsById(loanId)) {
-            throw new WorkingCapitalLoanNotFoundException(loanId);
-        }
+        final WorkingCapitalLoan loan = 
loanRepository.findById(loanId).orElseThrow(() -> new 
WorkingCapitalLoanNotFoundException(loanId));
 
         final MathContext mc = MoneyHelper.getMathContext();
-        final ProjectedAmortizationScheduleModel model = 
scheduleRepositoryWrapper.readModel(loanId, mc, DEFAULT_CURRENCY)
+        final CurrencyData currency = 
WorkingCapitalLoanAmortizationScheduleWriteServiceImpl.resolveCurrency(loan);

Review Comment:
   Static call into the write service impl from the read service. Extract 
`resolveCurrency` into its own helper / domain method on `WorkingCapitalLoan`. 
The read service shouldn't depend on the write service's impl class.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanPeriodPaymentRateChangeReadServiceImpl.java:
##########
@@ -0,0 +1,47 @@
+/**
+ * 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.portfolio.workingcapitalloan.service;
+
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import 
org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanPeriodPaymentRateChangeData;
+import 
org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanPeriodPaymentRateChange;
+import 
org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanPeriodPaymentRateChangeRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
+public class WorkingCapitalLoanPeriodPaymentRateChangeReadServiceImpl 
implements WorkingCapitalLoanPeriodPaymentRateChangeReadService {
+
+    private final WorkingCapitalLoanPeriodPaymentRateChangeRepository 
repository;
+
+    @Override
+    public List<WorkingCapitalLoanPeriodPaymentRateChangeData> 
retrieveRateChangeHistory(final Long loanId) {
+        return 
repository.findByWorkingCapitalLoanIdOrderByIdDesc(loanId).stream().map(e -> 
toData(e, loanId)).toList();

Review Comment:
   No check that the loan actually exists. Bogus `loanId` returns an empty list 
silently. Let's 404 on missing loan.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanWritePlatformServiceImpl.java:
##########
@@ -668,6 +671,57 @@ public CommandProcessingResult creditBalanceRefund(final 
Long loanId, final Json
                 
.withClientId(loan.getClientId()).withLoanId(loanId).with(changes).build();
     }
 
+    @Override
+    @Transactional
+    public CommandProcessingResult updatePeriodPaymentRate(final Long loanId, 
final JsonCommand command) {
+        final WorkingCapitalLoan loan = this.loanRepository.findById(loanId)
+                .orElseThrow(() -> new 
WorkingCapitalLoanNotFoundException(loanId));
+        this.validator.validateUpdatePeriodPaymentRate(command.json(), loan);
+
+        final BigDecimal newRate = 
this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanConstants.periodPaymentRateParamName,
+                command.parsedJson(), new HashSet<>());
+        final BigDecimal previousRate = 
loan.getLoanProductRelatedDetails().getPeriodPaymentRate();
+
+        final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+
+        final List<WorkingCapitalLoanPeriodPaymentRateChange> activeChanges = 
this.rateChangeRepository
+                .findByWorkingCapitalLoanIdAndReversedFalse(loanId);
+        for (final WorkingCapitalLoanPeriodPaymentRateChange active : 
activeChanges) {
+            active.reverse(businessDate);
+        }
+        if (!activeChanges.isEmpty()) {
+            this.rateChangeRepository.saveAll(activeChanges);

Review Comment:
   Why call `saveAll`? The entities you fetched are managed within the 
`@Transactional` method. Same for `loanRepository.saveAndFlush(loan)` further 
below.



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanPeriodPaymentRateChange.java:
##########
@@ -0,0 +1,79 @@
+/**
+ * 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.portfolio.workingcapitalloan.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.Version;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import 
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@Entity
+@Table(name = "m_wc_loan_period_payment_rate_change")
+public class WorkingCapitalLoanPeriodPaymentRateChange extends 
AbstractAuditableWithUTCDateTimeCustom<Long> {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "wc_loan_id", nullable = false)
+    private WorkingCapitalLoan workingCapitalLoan;
+
+    @Column(name = "effective_date", nullable = false)
+    private LocalDate effectiveDate;
+
+    @Column(name = "previous_rate", scale = 6, precision = 19, nullable = 
false)
+    private BigDecimal previousRate;
+
+    @Column(name = "new_rate", scale = 6, precision = 19, nullable = false)
+    private BigDecimal newRate;
+
+    @Column(name = "is_reversed", nullable = false)
+    private boolean reversed;

Review Comment:
   Nothing currently writes `reversedByUser` or other reversal audit fields for 
reversal - is that intentional, or are we missing reversal audit columns vs the 
`m_wc_loan_transaction` reversal pattern?



##########
fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleWriteServiceImpl.java:
##########
@@ -180,21 +183,56 @@ public RepaymentAmortizationData applyRepayment(final 
WorkingCapitalLoan loan, f
     private BigDecimal sumRunningNpv(final ProjectedAmortizationScheduleModel 
model) {
         final MathContext mc = MoneyHelper.getMathContext();
         BigDecimal result = BigDecimal.ZERO;
-        for (ProjectedPayment payment : model.payments()) {
+        for (ProjectedPayment payment : model.projectedPayments()) {
             if (payment.paymentNo() > 0 && payment.npvValue() != null && 
payment.npvValue().getAmount() != null) {
                 result = result.add(payment.npvValue().getAmount(), mc);
             }
         }
         return result;
     }
 
-    private MonetaryCurrency resolveCurrency(final WorkingCapitalLoan loan) {
+    @Override
+    public void regenerateAmortizationScheduleOnRateChange(final 
WorkingCapitalLoan loan, final BigDecimal newRate) {
+        Validate.notNull(loan, "loan must not be null");
+        Validate.notNull(newRate, "newRate must not be null");
+
+        final MathContext mc = MoneyHelper.getMathContext();
+        final CurrencyData currency = resolveCurrency(loan);
+        final ProjectedAmortizationScheduleModel model = 
scheduleRepositoryWrapper.readModel(loan.getId(), mc, currency)
+                .orElseThrow(() -> new IllegalStateException("Projected 
amortization schedule is not found for loan " + loan.getId()));
+
+        final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+        final LocalDate loanDisbursementDate = 
resolveLoanDisbursementDate(loan);
+        final int splitDayIndex = (int) 
ChronoUnit.DAYS.between(loanDisbursementDate, businessDate);
+        final LocalDate modelRateChangeDate = 
model.expectedDisbursementDate().plusDays(splitDayIndex);
+
+        model.clearLastRateSegment();
+
+        calculator.applyRateChange(model, newRate, modelRateChangeDate);
+
+        scheduleRepositoryWrapper.writeModel(loan, model);
+    }
+
+    private LocalDate resolveLoanDisbursementDate(final WorkingCapitalLoan 
loan) {
+        if (loan.getDisbursementDetails() != null && 
!loan.getDisbursementDetails().isEmpty()) {
+            final WorkingCapitalLoanDisbursementDetails detail = 
loan.getDisbursementDetails().getFirst();
+            if (detail.getActualDisbursementDate() != null) {
+                return detail.getActualDisbursementDate();
+            }
+            if (detail.getExpectedDisbursementDate() != null) {
+                return detail.getExpectedDisbursementDate();

Review Comment:
   The validator only allows rate changes for `LoanStatus.ACTIVE`. An active 
loan must have an actual disbursement date - so taking the first disbursement 
detail and falling back to expected is suspicious. Shouldn't we be using the 
actual disbursement date strictly here, and throwing a proper domain exception 
(not `IllegalStateException`) if it's missing?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to