Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 44 additions & 9 deletions src/app/features/products/loan-product-form.component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,33 +87,42 @@ const TEMPLATE = {
buyDownFeeCalculationTypeOptions: [{ code: 'FLAT', value: 'Flat' }],
buyDownFeeStrategyOptions: [{ code: 'EQUAL_AMORTIZATION', value: EQUAL_AMORTIZATION_LABEL }],
interestRecalculationCompoundingTypeOptions: [
{ id: 0, code: 'interestRecalculationCompoundingMethod.none', description: 'None' },
{ id: 1, code: 'interestRecalculationCompoundingMethod.fee', description: 'Fee' },
{ id: 0, code: 'interestRecalculationCompoundingMethod.none', value: 'None' },
{ id: 1, code: 'interestRecalculationCompoundingMethod.fee', value: 'Fee' },
],
interestRecalculationFrequencyTypeOptions: [
{
id: 1,
code: 'interestRecalculationFrequencyType.same.as.repayment.period',
description: 'Same as repayment period',
value: 'Same as repayment period',
},
{ id: 2, code: 'interestRecalculationFrequencyType.daily', description: 'Daily' },
{ id: 2, code: 'interestRecalculationFrequencyType.daily', value: 'Daily' },
],
rescheduleStrategyTypeOptions: [
{
id: 1,
code: 'loanRescheduleStrategyMethod.reduce.emi.amount',
description: 'Reduce EMI amount',
value: 'Reduce EMI amount',
},
],
preClosureInterestCalculationStrategyOptions: [
{
id: 1,
code: 'preClosureInterestCalculationStrategy.tillPreClosureDate',
description: 'Till pre-close date',
code: 'loanPreClosureInterestCalculationStrategy.tillPreClosureDate',
value: 'Till pre-close date',
},
],
repaymentStartDateTypeOptions: [
Comment thread
1cbyc marked this conversation as resolved.
{ id: 1, code: 'repaymentStartDateType.disbursement', description: 'Disbursement date' },
{
id: 1,
code: 'repaymentStartDateType.disbursementDate',
value: 'Disbursement Date',
},
{
id: 2,
code: 'repaymentStartDateType.submittedOnDate',
value: 'Submitted On Date',
},
],
chargeOffBehaviourOptions: [
{ code: 'REGULAR', value: 'Regular' },
Expand Down Expand Up @@ -492,7 +501,33 @@ describe('LoanProductFormComponent', () => {
expect(component.capitalizedIncomeTypeOptions()).toHaveLength(2);
expect(component.capitalizedIncomeStrategyOptions()[0].value).toBe(EQUAL_AMORTIZATION_LABEL);
expect(component.chargeOffBehaviourOptions()).toHaveLength(2);
expect(component.repaymentStartDateTypeOptions()).toHaveLength(1);
expect(component.repaymentStartDateTypeOptions()).toHaveLength(2);
});

it('renders every template enum label from the live response shape', () => {
component.onInterestRecalculationChange(true);
component.onCompoundingMethodChange(1);
fixture.detectChanges();

const labels = (testId: string) =>
Array.from<HTMLElement>(
fixture.nativeElement
.querySelector(`[data-testid="${CSS.escape(testId)}"]`)
.querySelectorAll('ion-select-option'),
).map((option) => option.textContent.trim());

expect(labels('loan-product-compounding-method')).toEqual(['None', 'Fee']);
expect(labels('loan-product-reschedule-strategy')).toEqual(['Reduce EMI amount']);
expect(labels('loan-product-rest-frequency')).toEqual(['Same as repayment period', 'Daily']);
expect(labels('loan-product-compounding-frequency')).toEqual([
'Same as repayment period',
'Daily',
]);
expect(labels('loan-product-pre-closure-strategy')).toEqual(['Till pre-close date']);
expect(labels('loan-product-repayment-start-date-type')).toEqual([
'Disbursement Date',
'Submitted On Date',
]);
});
});

Expand Down
41 changes: 28 additions & 13 deletions src/app/features/products/loan-product-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ import { TooltipDirective } from '../../shared/directives/tooltip.directive';
*/
const DAILY_INTEREST_CALCULATION_PERIOD = 0;

/**
* Fineract serializes enum options as `{ id, code, value }` (`BaseEnumOptionData`). The generated
* loan-product models declare `description` instead, which the server does not send; see #653.
* Drop this compatibility type once the models are regenerated from the corrected contract.
*/
type TemplateEnumOption<T> = T & { value?: string };

@Component({
selector: 'app-loan-product-form',
standalone: true,
Expand Down Expand Up @@ -992,7 +999,7 @@ const DAILY_INTEREST_CALCULATION_PERIOD = 0;
>
@for (option of compoundingTypeOptions(); track option.id) {
<ion-select-option [value]="option.id">{{
option.description
enumLabel(option)
}}</ion-select-option>
}
</ion-select>
Expand All @@ -1019,7 +1026,7 @@ const DAILY_INTEREST_CALCULATION_PERIOD = 0;
>
@for (option of rescheduleStrategyOptions(); track option.id) {
<ion-select-option [value]="option.id">{{
option.description
enumLabel(option)
}}</ion-select-option>
}
</ion-select>
Expand All @@ -1046,7 +1053,7 @@ const DAILY_INTEREST_CALCULATION_PERIOD = 0;
>
@for (option of recalculationFrequencyOptions(); track option.id) {
<ion-select-option [value]="option.id">{{
option.description
enumLabel(option)
}}</ion-select-option>
}
</ion-select>
Expand Down Expand Up @@ -1098,7 +1105,7 @@ const DAILY_INTEREST_CALCULATION_PERIOD = 0;
>
@for (option of recalculationFrequencyOptions(); track option.id) {
<ion-select-option [value]="option.id">{{
option.description
enumLabel(option)
}}</ion-select-option>
}
</ion-select>
Expand Down Expand Up @@ -1152,7 +1159,7 @@ const DAILY_INTEREST_CALCULATION_PERIOD = 0;
>
@for (option of preClosureStrategyOptions(); track option.id) {
<ion-select-option [value]="option.id">{{
option.description
enumLabel(option)
}}</ion-select-option>
}
</ion-select>
Expand Down Expand Up @@ -1216,7 +1223,7 @@ const DAILY_INTEREST_CALCULATION_PERIOD = 0;
>
@for (option of repaymentStartDateTypeOptions(); track option.id) {
<ion-select-option [value]="option.id">{{
option.description
enumLabel(option)
}}</ion-select-option>
}
</ion-select>
Expand Down Expand Up @@ -1421,17 +1428,21 @@ export class LoanProductFormComponent implements OnInit {
readonly compoundingMethod = signal<number | undefined>(undefined);
readonly restFrequencyType = signal<number | undefined>(undefined);
readonly compoundingFrequencyType = signal<number | undefined>(undefined);
readonly compoundingTypeOptions = signal<GetLoanProductsInterestRecalculationCompoundingType[]>(
[],
);
readonly compoundingTypeOptions = signal<
TemplateEnumOption<GetLoanProductsInterestRecalculationCompoundingType>[]
>([]);
readonly recalculationFrequencyOptions = signal<
GetLoanProductsInterestRecalculationCompoundingFrequencyType[]
TemplateEnumOption<GetLoanProductsInterestRecalculationCompoundingFrequencyType>[]
>([]);
readonly rescheduleStrategyOptions = signal<
TemplateEnumOption<GetLoanProductsRescheduleStrategyType>[]
>([]);
readonly rescheduleStrategyOptions = signal<GetLoanProductsRescheduleStrategyType[]>([]);
readonly preClosureStrategyOptions = signal<
GetLoanProductsPreClosureInterestCalculationStrategy[]
TemplateEnumOption<GetLoanProductsPreClosureInterestCalculationStrategy>[]
>([]);
readonly repaymentStartDateTypeOptions = signal<
TemplateEnumOption<GetLoanProductsRepaymentStartDateType>[]
>([]);
readonly repaymentStartDateTypeOptions = signal<GetLoanProductsRepaymentStartDateType[]>([]);
readonly chargeOffBehaviourOptions = signal<StringEnumOptionData[]>([]);

/**
Expand Down Expand Up @@ -1497,6 +1508,10 @@ export class LoanProductFormComponent implements OnInit {
this.product.update((product) => ({ ...product, [field]: value }));
}

protected enumLabel(option: { value?: string; description?: string; code?: string }): string {
return option.value ?? option.description ?? option.code ?? '';
Comment thread
Aman-Mittal marked this conversation as resolved.
}

/** Applies multiple related product fields in one signal notification. */
private patchProductFields(patch: Partial<PostLoanProductsRequest>): void {
this.product.update((product) => ({ ...product, ...patch }));
Expand Down
Loading