adamsaghy commented on code in PR #6546:
URL: https://github.com/apache/fineract/pull/6546#discussion_r4386911560


##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java:
##########
@@ -328,13 +346,17 @@ private LoanApplicationTerms 
assembleLoanApplicationTermsFrom(final JsonElement
          */
         if (calculatedRepaymentsStartingFromDate == null) {
             LocalDate tmpCalculatedRepaymentsStartingFromDate = 
deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
-                    repaymentPeriodFrequencyType, 0, calendar, 
submittedOnDate, repaymentStartDateType, monthEndDueDateStrategy);
+                    repaymentPeriodFrequencyType, 0, calendar, 
submittedOnDate, repaymentStartDateType, semiMonthlyDueDays,
+                    monthEndDueDateStrategy);
             calculatedRepaymentsStartingFromDate = 
deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
                     repaymentPeriodFrequencyType, 
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar, 
submittedOnDate,
-                    repaymentStartDateType, monthEndDueDateStrategy);
+                    repaymentStartDateType, semiMonthlyDueDays, 
monthEndDueDateStrategy);
             if 
(!tmpCalculatedRepaymentsStartingFromDate.equals(calculatedRepaymentsStartingFromDate))
 {
                 repaymentsStartingFromDate = 
calculatedRepaymentsStartingFromDate;
             }
+        } else if (repaymentPeriodFrequencyType.isSemiMonthly() && calendar == 
null) {

Review Comment:
   This check only runs on the JSON path (create / schedule preview). Modify 
regenerates the schedule from the loan itself (`LoanAssemblerImpl` → 
`regenerateRepaymentSchedule(loan, ...)`), and neither 
`validateForModify(command, loan)` nor `validateForModify(loan)` checks the 
first repayment date against the due days. So both of these get through:
   - `PUT` with `repaymentsStartingFromDate` on a non-due day
   - `PUT` that changes the due days so the stored 
`expectedFirstRepaymentOnDate` is no longer one of them
   
   The first installment then lands on a non-configured day and the rest follow 
the new days. `aFirstRepaymentDateOffTheDueDaysIsRejected` only covers creation.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java:
##########
@@ -690,6 +719,16 @@ private void 
validateRepaymentsStartDateWithMeetingDates(final LocalDate repayme
         }
     }
 
+    private void validateRepaymentsStartDateIsSemiMonthlyDueDate(final 
LocalDate repaymentsStartingFromDate,
+            final SemiMonthlyDueDays semiMonthlyDueDays) {
+        if (!SemiMonthlyScheduleDates.isDueDate(repaymentsStartingFromDate, 
semiMonthlyDueDays)) {
+            final String errorMessage = "First repayment date '" + 
repaymentsStartingFromDate
+                    + "' does not fall on a due day of the semi-monthly 
schedule";
+            throw new 
LoanApplicationDateException("first.repayment.date.not.a.semi.monthly.due.day", 
errorMessage,

Review Comment:
   Nit: the PR description says validation errors return 400, but 
`LoanApplicationDateException` is a domain-rule exception, so this one is 403 
(the IT asserts 403). Either update the description or make it a validation 
error.



##########
fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java:
##########
@@ -1080,7 +1096,21 @@ public void validateForModify(final JsonCommand command, 
final Loan loan) {
                 final Integer repaymentEveryType = this.fromApiJsonHelper
                         
.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentFrequencyTypeParameterName,
 element);
                 
baseDataValidator.reset().parameter(LoanApiConstants.repaymentFrequencyTypeParameterName).value(repaymentEveryType)
-                        .notNull().inMinMaxRange(0, 3);
+                        
.notNull().isOneOfTheseValues(SUPPORTED_PERIOD_FREQUENCY_TYPES);
+                // A loan being modified keeps the days it already has before 
falling back to the product's.
+                final LoanProductRelatedDetail dueDaysFallback = 
loan.getLoanRepaymentScheduleDetail().getFirstRepaymentDayOfMonth() != null
+                        ? loan.getLoanRepaymentScheduleDetail()
+                        : loanProduct.getLoanProductRelatedDetail();
+                validateSemiMonthlyRepayment(element, baseDataValidator, 
repaymentEveryType, repaymentEvery,
+                        dueDaysFallback.getFirstRepaymentDayOfMonth(), 
dueDaysFallback.getSecondRepaymentDayOfMonth());
+            } else if 
(this.fromApiJsonHelper.parameterExists(LoanApiConstants.FIRST_REPAYMENT_DAY_OF_MONTH,
 element)
+                    || 
this.fromApiJsonHelper.parameterExists(LoanApiConstants.SECOND_REPAYMENT_DAY_OF_MONTH,
 element)) {
+                // Only the days are changing, so they are checked against the 
frequency the loan already has.

Review Comment:
   `repaymentEvery` must be 1 is only enforced on modify when the request 
carries `repaymentFrequencyType` or one of the due days. A `PUT` with just 
`repaymentEvery: 2` (plus a matching `loanTermFrequency`) on a semi-monthly 
loan skips both branches and is accepted.
   
   Result: due dates stay half-monthly (`SemiMonthlyScheduleDates.next` ignores 
`repaymentEvery`), but interest does not:
   - progressive, same-as-repayment: rate factor is `rate * 2 / 24` per 
half-month period, i.e. double interest
   - cumulative: EMI is solved with `rate * 2 / 24` while each period is 
charged `rate / 24`
   
   The same branching also skips the `calendarId` rejection on modify.
   
   Suggest running the semi-monthly check against the effective 
(request-or-stored) frequency unconditionally, as 
`LoanProductDataValidator#validateForUpdate` already does.



-- 
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