update loan product code and rd,fd,saving,current account validation add
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ed45c8637e
commit
d0370781e8
+5
-3
@@ -9,7 +9,7 @@
|
||||
$primary: #1353a0;
|
||||
|
||||
.lp-container {
|
||||
max-width: 960px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
@@ -53,11 +53,11 @@ $primary: #1353a0;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body {
|
||||
overflow: visible !important;
|
||||
overflow: hidden visible !important;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-content {
|
||||
overflow: visible !important;
|
||||
overflow: hidden visible !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,8 @@ $primary: #1353a0;
|
||||
|
||||
.lp-tab-content {
|
||||
padding: 24px 16px;
|
||||
min-width: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.lp-action-bar {
|
||||
|
||||
+30
@@ -90,4 +90,34 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Classification + GL Account — combined section -->
|
||||
<div class="lpd-section">
|
||||
<div class="lpd-section__header">
|
||||
<i class="fas fa-sitemap"></i>
|
||||
<h3>Product Classification</h3>
|
||||
</div>
|
||||
<div class="lpd-grid">
|
||||
<mat-form-field appearance="outline" class="lpd-field">
|
||||
<mat-label>Product Type Group</mat-label>
|
||||
<mat-select formControlName="productTypeGroup">
|
||||
@for (group of productTypeGroups; track group.id) {
|
||||
<mat-option [value]="group.id">{{ group.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@if (loanProductDetailsForm.get('productTypeGroup')?.value) {
|
||||
<mat-form-field appearance="outline" class="lpd-field">
|
||||
<mat-label>GL Account ({{ glClassificationLabel }})</mat-label>
|
||||
<mat-select formControlName="glAccount">
|
||||
@for (acc of availableGlAccounts; track acc.id) {
|
||||
<mat-option [value]="acc.id"> ({{ acc.glCode }}) {{ acc.name }} </mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>GL Account is required</mat-error>
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+137
@@ -68,3 +68,140 @@ $gray-900: #202124;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
// GL Section
|
||||
.lpd-gl-section {
|
||||
border-top: 2px solid #e0f2fe;
|
||||
}
|
||||
|
||||
.lpd-gl-subtype-badge {
|
||||
margin-left: auto;
|
||||
padding: 3px 12px;
|
||||
background: #eff6ff;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 20px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.lpd-gl-body {
|
||||
padding: 16px 24px 20px;
|
||||
}
|
||||
|
||||
.lpd-gl-hint {
|
||||
font-size: 12.5px;
|
||||
color: #64748b;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.lpd-gl-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.lpd-gl-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 14px 18px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s,
|
||||
background 0.15s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
border-color: #93c5fd;
|
||||
background: #f8faff;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
border-color: #2563eb !important;
|
||||
background: #eff6ff !important;
|
||||
box-shadow: 0 0 0 3px rgb(37 99 235 / 10%);
|
||||
|
||||
.lpd-gl-card__check {
|
||||
opacity: 1;
|
||||
color: #2563eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lpd-gl-radio {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lpd-gl-card__icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--asset {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
&--liability {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
}
|
||||
|
||||
.lpd-gl-card__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.lpd-gl-card__title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.lpd-gl-card__desc {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.lpd-gl-card__check {
|
||||
font-size: 18px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.lpd-gl-account-field {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.lpd-gl-type-badge {
|
||||
padding: 3px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 700;
|
||||
margin-left: 6px;
|
||||
|
||||
&--asset {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
&--liability {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
}
|
||||
|
||||
+74
-1
@@ -57,6 +57,68 @@ export class LoanProductDetailsStepComponent implements OnInit {
|
||||
startDate: this.loanProductsTemplate.startDate && new Date(this.loanProductsTemplate.startDate),
|
||||
closeDate: this.loanProductsTemplate.closeDate && new Date(this.loanProductsTemplate.closeDate)
|
||||
});
|
||||
|
||||
const mappingOptions = this.loanProductsTemplate?.accountingMappingOptions ?? {};
|
||||
this.assetAccountOptions = mappingOptions.assetAccountOptions ?? [];
|
||||
this.liabilityAccountOptions = mappingOptions.liabilityAccountOptions ?? [];
|
||||
}
|
||||
|
||||
/** Product Classification data */
|
||||
readonly productTypeGroups = [
|
||||
{ id: 'loans', label: 'Loan' },
|
||||
{ id: 'advanced', label: 'Advanced' }
|
||||
];
|
||||
|
||||
readonly productTypeSubGroups: Record<string, { id: string; label: string }[]> = {
|
||||
loans: [
|
||||
{ id: 'emi', label: 'EMI' },
|
||||
{ id: 'cc', label: 'CC (Cash Credit)' },
|
||||
{ id: 'overdraft', label: 'Overdraft' },
|
||||
{ id: 'warehouse', label: 'Warehouse' },
|
||||
{ id: 'gold', label: 'Gold' }
|
||||
],
|
||||
advanced: [
|
||||
{ id: 'fd', label: 'FD (Fixed Deposit)' },
|
||||
{ id: 'rd', label: 'RD (Recurring Deposit)' }
|
||||
]
|
||||
};
|
||||
|
||||
/** Default GL per product type group */
|
||||
private readonly glDefaultMap: Record<string, string> = {
|
||||
loans: 'asset',
|
||||
advanced: 'asset'
|
||||
};
|
||||
|
||||
get availableSubGroups() {
|
||||
const group = this.loanProductDetailsForm.get('productTypeGroup')?.value;
|
||||
return group ? (this.productTypeSubGroups[group] ?? []) : [];
|
||||
}
|
||||
|
||||
get glOptions(): string[] {
|
||||
return [
|
||||
'asset',
|
||||
'liability'
|
||||
];
|
||||
}
|
||||
|
||||
assetAccountOptions: any[] = [];
|
||||
liabilityAccountOptions: any[] = [];
|
||||
|
||||
get availableGlAccounts(): any[] {
|
||||
const type = this.loanProductDetailsForm.get('glClassification')?.value;
|
||||
if (type === 'asset') return this.assetAccountOptions;
|
||||
if (type === 'liability') return this.liabilityAccountOptions;
|
||||
return [];
|
||||
}
|
||||
|
||||
get glClassificationLabel(): string {
|
||||
const type = this.loanProductDetailsForm.get('glClassification')?.value;
|
||||
return type === 'liability' ? 'Liability' : 'Asset';
|
||||
}
|
||||
|
||||
get selectedGroupLabel(): string {
|
||||
const group = this.loanProductDetailsForm.get('productTypeGroup')?.value;
|
||||
return this.productTypeGroups.find((g) => g.id === group)?.label ?? '';
|
||||
}
|
||||
|
||||
createLoanProductDetailsForm() {
|
||||
@@ -71,7 +133,18 @@ export class LoanProductDetailsStepComponent implements OnInit {
|
||||
],
|
||||
description: [''],
|
||||
startDate: [''],
|
||||
closeDate: ['']
|
||||
closeDate: [''],
|
||||
productTypeGroup: [''],
|
||||
productTypeSubGroup: [''],
|
||||
glClassification: ['asset'],
|
||||
glAccount: ['']
|
||||
});
|
||||
|
||||
// Auto-set GL based on product type group
|
||||
this.loanProductDetailsForm.get('productTypeGroup')?.valueChanges.subscribe((group: string) => {
|
||||
const defaultVal = this.glDefaultMap[group] ?? 'asset';
|
||||
this.loanProductDetailsForm.get('glClassification')?.setValue(defaultVal);
|
||||
this.loanProductDetailsForm.get('glAccount')?.setValue('');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+132
-29
@@ -467,7 +467,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Against Interest Calculation Enabled</mat-label>
|
||||
<mat-select formControlName="againstInterestCalculationEnabled">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -499,7 +499,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-3">
|
||||
<mat-label>Consider Debit Closing Amount For Against Clearing</mat-label>
|
||||
<mat-select formControlName="considerDebitClosingAmountForAgainstClearing">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -517,7 +517,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Penalty Interest Calculation Enabled</mat-label>
|
||||
<mat-select formControlName="penaltyInterestCalculationEnabled">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -525,7 +525,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Penalty Interest Calculation Method</mat-label>
|
||||
<mat-select formControlName="penaltyInterestCalculationMethod">
|
||||
<mat-option value="Daywise ( 36500 )">Daywise ( 36500 )</mat-option>
|
||||
@@ -559,7 +559,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Interest On Credit Balance</mat-label>
|
||||
<mat-select formControlName="interestOnCreditBalance">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -567,7 +567,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Penalty Interest Date Different Than Regular Interest</mat-label>
|
||||
<mat-select formControlName="penaltyInterestDateDifferentThanRegularInterest">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -575,7 +575,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Fixed Deposit Overdraft Certificate Sort Order</mat-label>
|
||||
<mat-select formControlName="fixedDepositOverdraftCertificateSortOrder">
|
||||
<mat-option value="ORDER ON INTEREST RATE">Order on Interest Rate</mat-option>
|
||||
@@ -599,12 +599,12 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Minimum Normal Interest Amount</mat-label>
|
||||
<input type="number" matInput formControlName="minimumNormalInterestAmount" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Minimum Interest Calculation Method</mat-label>
|
||||
<mat-select formControlName="minimumInterestCalculationMethod">
|
||||
<mat-option value="Flat Amount">Flat Amount</mat-option>
|
||||
@@ -613,12 +613,12 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Minimum Against Clearing Interest Amount</mat-label>
|
||||
<input type="number" matInput formControlName="minimumAgainstClearingInterestAmount" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Limit Increase Upto Interest Amount In Interest Calculation</mat-label>
|
||||
<mat-select formControlName="limitIncreaseUptoInterestAmountInInterestCalculation">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -626,22 +626,22 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Minimum Penalty Interest Amount</mat-label>
|
||||
<input type="number" matInput formControlName="minimumPenaltyInterestAmount" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Days For Limit Increase Upto Interest Amount</mat-label>
|
||||
<input type="number" matInput formControlName="daysForLimitIncreaseUptoInterestAmount" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Additional Interest Rate For Fixed Deposit Overdraft</mat-label>
|
||||
<input type="number" matInput formControlName="additionalInterestRateForFixedDepositOverdraft" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Consider DD/Payorder In Against Clearing Interest Calculation</mat-label>
|
||||
<mat-select formControlName="considerDdPayorderInAgainstClearingInterestCalculation">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -649,12 +649,12 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Grace Days After Renewal Date</mat-label>
|
||||
<input type="number" matInput formControlName="graceDaysAfterRenewDate" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Apply Undebited Interest Concept Enabled</mat-label>
|
||||
<mat-select formControlName="applyUndebitedInterestConceptEnabled">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -671,12 +671,12 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Normal Grace Days After Overdue</mat-label>
|
||||
<input type="number" matInput formControlName="normalGraceDaysAfterOverdue" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Allow Against Clearing Entry</mat-label>
|
||||
<mat-select formControlName="allowAgainstClearingEntry">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -684,7 +684,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Allow No Balance Entry</mat-label>
|
||||
<mat-select formControlName="allowNoBalanceEntry">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -692,7 +692,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Fixed Deposit Overdraft Penalty Interest Combined With Normal Interest</mat-label>
|
||||
<mat-select formControlName="fixedDepositOverdraftPenaltyInterestCombinedWithNormalInterest">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -700,7 +700,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Allow No Balance Entry With Password</mat-label>
|
||||
<mat-select formControlName="allowNoBalanceEntryWithPassword">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -708,7 +708,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Undebited Simple Interest Account Wise</mat-label>
|
||||
<mat-select formControlName="undebitedSimpleInterestAccountWise">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -716,7 +716,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Daily Interest Calculation Enabled</mat-label>
|
||||
<mat-select formControlName="dailyInterestCalculationEnabled">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -724,12 +724,12 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Daily Interest Calculation Start Date</mat-label>
|
||||
<input type="text" matInput formControlName="dailyInterestCalculationStartDate" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Non Performing Asset Penal Interest Rate Update</mat-label>
|
||||
<mat-select formControlName="nonPerformingAssetPenalInterestRateUpdate">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -737,12 +737,12 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Non Performing Asset Penal Interest Rate</mat-label>
|
||||
<input type="number" matInput formControlName="nonPerformingAssetPenalInterestRate" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Fixed Deposit Overdraft Penal Interest Rate Update In Branch Process</mat-label>
|
||||
<mat-select formControlName="fixedDepositOverdraftPenalInterestRateUpdateInBranchProcess">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
@@ -750,13 +750,116 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Add Penal Interest Rate</mat-label>
|
||||
<input type="number" matInput formControlName="addPenalInterestRate" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RBI Compliance & KFS Settings -->
|
||||
<div class="lps-section">
|
||||
<div class="lps-section__header">
|
||||
<i class="fas fa-balance-scale lps-section__icon"></i>
|
||||
<h3 class="lps-section__title">RBI Compliance & KFS Settings</h3>
|
||||
</div>
|
||||
<div class="lps-section__body lps-grid-4">
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Penal Charge Method</mat-label>
|
||||
<mat-select formControlName="penalChargeMethod">
|
||||
<mat-option value="RBI COMPLIANT PENAL CHARGES">RBI Compliant Penal Charges (Non-Compounding)</mat-option>
|
||||
<mat-option value="PENAL INTEREST">Legacy Penal Interest (Compounding)</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="lps-checkbox-row lps-span-2">
|
||||
<mat-checkbox labelPosition="before" formControlName="isFloatingRate">
|
||||
Is Floating Rate Loan? (Individual)
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Foreclosure Charges Applicable</mat-label>
|
||||
<mat-select formControlName="foreclosureChargesApplicable">
|
||||
<mat-option value="YES">YES</mat-option>
|
||||
<mat-option value="NO">NO</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint>RBI: No charges for Floating Rate Loans to Individuals</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="lps-checkbox-row">
|
||||
<mat-checkbox labelPosition="before" formControlName="keyFactStatementEnabled">
|
||||
Enable Key Fact Statement (KFS)
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="lps-checkbox-row">
|
||||
<mat-checkbox labelPosition="before" formControlName="annualPercentageRateCalculationEnabled">
|
||||
Calculate APR
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Credit Bureau Settings (CIBIL) -->
|
||||
<div class="lps-section">
|
||||
<div class="lps-section__header">
|
||||
<i class="fas fa-file-invoice lps-section__icon"></i>
|
||||
<h3 class="lps-section__title">Credit Bureau Settings (CIBIL/Highmark)</h3>
|
||||
</div>
|
||||
<div class="lps-section__body lps-grid-4">
|
||||
<mat-form-field appearance="outline" class="lps-full-width">
|
||||
<mat-label>Loan Product Category (RBI Reporting)</mat-label>
|
||||
<mat-select formControlName="loanProductCategory">
|
||||
<mat-option value="PERSONAL_LOAN">Personal/Consumer Loan (Code 02)</mat-option>
|
||||
<mat-option value="HOUSING_LOAN">Housing/Mortgage Loan (Code 15)</mat-option>
|
||||
<mat-option value="BUSINESS_LOAN">Business/Commercial Loan (Code 50)</mat-option>
|
||||
<mat-option value="VEHICLE_LOAN">Vehicle/Auto Loan (Code 04)</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint>Selecting a category will auto-fill the credit bureau account types below.</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>CIBIL Account Type</mat-label>
|
||||
<input type="text" matInput formControlName="cibilAccountType" placeholder="e.g. 02" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>CIBIL Commercial Account Type</mat-label>
|
||||
<input type="text" matInput formControlName="cibilCommercialAccountType" placeholder="e.g. 0420" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Highmark Account Type</mat-label>
|
||||
<input type="text" matInput formControlName="highmarkAccountType" placeholder="e.g. 02" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Highmark Commercial Account Type</mat-label>
|
||||
<input type="text" matInput formControlName="highmarkCommercialAccountType" placeholder="e.g. 0420" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Experian Account Type</mat-label>
|
||||
<input type="text" matInput formControlName="experianAccountType" placeholder="e.g. 02" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Experian Commercial Account Type</mat-label>
|
||||
<input type="text" matInput formControlName="experianCommercialAccountType" placeholder="e.g. 0420" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Equifax Account Type</mat-label>
|
||||
<input type="text" matInput formControlName="equifaxAccountType" placeholder="e.g. 02" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="lps-span-2">
|
||||
<mat-label>Equifax Commercial Account Type</mat-label>
|
||||
<input type="text" matInput formControlName="equifaxCommercialAccountType" placeholder="e.g. 0420" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Moratorium & Grace -->
|
||||
<div class="lps-section">
|
||||
<div class="lps-section__header">
|
||||
|
||||
+8
@@ -75,6 +75,14 @@
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.lps-span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.lps-span-3 {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.lps-checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+56
-1
@@ -408,13 +408,68 @@ export class LoanProductSettingsStepComponent implements OnInit {
|
||||
nonPerformingAssetPenalInterestRateUpdate: ['YES'],
|
||||
nonPerformingAssetPenalInterestRate: [''],
|
||||
fixedDepositOverdraftPenalInterestRateUpdateInBranchProcess: ['YES'],
|
||||
addPenalInterestRate: ['']
|
||||
addPenalInterestRate: [''],
|
||||
|
||||
// Credit Bureau Settings (CIBIL)
|
||||
cibilAccountType: [''],
|
||||
cibilCommercialAccountType: [''],
|
||||
highmarkAccountType: [''],
|
||||
highmarkCommercialAccountType: [''],
|
||||
experianAccountType: [''],
|
||||
experianCommercialAccountType: [''],
|
||||
equifaxAccountType: [''],
|
||||
equifaxCommercialAccountType: [''],
|
||||
|
||||
// RBI Compliance & Product Category
|
||||
loanProductCategory: [''],
|
||||
penalChargeMethod: ['RBI COMPLIANT PENAL CHARGES'],
|
||||
isFloatingRate: [false],
|
||||
foreclosureChargesApplicable: ['NO'],
|
||||
keyFactStatementEnabled: [true],
|
||||
annualPercentageRateCalculationEnabled: [true]
|
||||
});
|
||||
}
|
||||
|
||||
private autoFillCibilFields(category: string) {
|
||||
const cibilMappings: any = {
|
||||
PERSONAL_LOAN: { ac: '02', comm: '0420' },
|
||||
HOUSING_LOAN: { ac: '15', comm: '9004' },
|
||||
BUSINESS_LOAN: { ac: '50', comm: '0100' },
|
||||
VEHICLE_LOAN: { ac: '04', comm: '9004' }
|
||||
};
|
||||
|
||||
const mapping = cibilMappings[category];
|
||||
if (mapping) {
|
||||
this.loanProductSettingsForm.patchValue({
|
||||
cibilAccountType: mapping.ac,
|
||||
cibilCommercialAccountType: mapping.comm,
|
||||
highmarkAccountType: mapping.ac,
|
||||
highmarkCommercialAccountType: mapping.comm,
|
||||
experianAccountType: mapping.ac,
|
||||
experianCommercialAccountType: mapping.comm,
|
||||
equifaxAccountType: mapping.ac,
|
||||
equifaxCommercialAccountType: mapping.comm
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setConditionalControls() {
|
||||
const allowAttributeOverrides = this.loanProductSettingsForm.get('allowAttributeOverrides');
|
||||
|
||||
// RBI & CIBIL Category Listener
|
||||
this.loanProductSettingsForm.get('loanProductCategory').valueChanges.subscribe((category: string) => {
|
||||
if (category) {
|
||||
this.autoFillCibilFields(category);
|
||||
}
|
||||
});
|
||||
|
||||
// Floating Rate Logic (RBI: No foreclosure charges for floating rate loans to individuals)
|
||||
this.loanProductSettingsForm.get('isFloatingRate').valueChanges.subscribe((isFloating: boolean) => {
|
||||
if (isFloating) {
|
||||
this.loanProductSettingsForm.patchValue({ foreclosureChargesApplicable: 'NO' });
|
||||
}
|
||||
});
|
||||
|
||||
this.loanProductSettingsForm.get('daysInYearType').valueChanges.subscribe((daysInYearType: any) => {
|
||||
if (this.isAdvancedTransactionProcessingStrategy) {
|
||||
this.useDaysInYearCustomStrategy = daysInYearType == 1;
|
||||
|
||||
+87
@@ -264,6 +264,93 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Interest Rate Slabs Section -->
|
||||
<div class="lpt-section">
|
||||
<div class="lpt-section-header">
|
||||
<i class="fas fa-table lpt-section-icon"></i>
|
||||
<h3>Interest Rate Slabs</h3>
|
||||
</div>
|
||||
|
||||
<div class="lpt-slab-c">
|
||||
<!-- Particular Name -->
|
||||
<div class="lpt-slab-c-particular">
|
||||
<label class="lpt-slab-c-particular-label">Particular</label>
|
||||
<input
|
||||
type="text"
|
||||
class="lpt-slab-c-name-input"
|
||||
[formControl]="particularLabel"
|
||||
placeholder="e.g. Consumer Loan"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 2-column slab grid (same as before, General + Special inside each card) -->
|
||||
<div class="lpt-slab-c-grid">
|
||||
@for (genGroup of slabGroups; track $index) {
|
||||
<div class="lpt-slab-c-item">
|
||||
<span class="lpt-slab-c-range">{{ interestSlabDefinitions[$index].label }}</span>
|
||||
<div class="lpt-slab-c-rate-pair">
|
||||
<!-- General -->
|
||||
<div class="lpt-slab-c-rate-field" [formGroup]="genGroup">
|
||||
<span class="lpt-slab-c-rate-label">Gen</span>
|
||||
<div class="lpt-slab-c-input-wrap">
|
||||
<input
|
||||
type="number"
|
||||
class="lpt-slab-c-input"
|
||||
formControlName="interestRate"
|
||||
placeholder="0.00"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
/>
|
||||
<span class="lpt-slab-c-suffix">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Special -->
|
||||
<div class="lpt-slab-c-rate-field" [formGroup]="specialSlabGroups[$index]">
|
||||
<span class="lpt-slab-c-rate-label lpt-slab-c-rate-label--special">Spl</span>
|
||||
<div class="lpt-slab-c-input-wrap">
|
||||
<input
|
||||
type="number"
|
||||
class="lpt-slab-c-input lpt-slab-c-input--special"
|
||||
formControlName="interestRate"
|
||||
placeholder="0.00"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
/>
|
||||
<span class="lpt-slab-c-suffix">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Fixed Interest Rate row -->
|
||||
<div class="lpt-slab-c-fixed" [formGroup]="fixedRateGroup">
|
||||
<label class="lpt-slab-c-fixed-toggle">
|
||||
<input type="checkbox" formControlName="hasFixed" class="lpt-slab-c-checkbox" />
|
||||
<i class="fas fa-lock"></i>
|
||||
<span>Fixed Interest Rate (%)</span>
|
||||
</label>
|
||||
@if (fixedRateGroup.get('hasFixed').value) {
|
||||
<div class="lpt-slab-c-input-wrap">
|
||||
<input
|
||||
type="number"
|
||||
class="lpt-slab-c-input lpt-slab-c-input--fixed"
|
||||
formControlName="fixedAmount"
|
||||
placeholder="0.00"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
/>
|
||||
<span class="lpt-slab-c-suffix">%</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Repayment Schedule Section -->
|
||||
<div class="lpt-section">
|
||||
<div class="lpt-section-header">
|
||||
|
||||
+352
-30
@@ -6,14 +6,42 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// ─── Design tokens ────────────────────────────────────────────────────────────
|
||||
$primary: #2563eb;
|
||||
$primary-light: #eff6ff;
|
||||
$primary-border: #bfdbfe;
|
||||
$surface: #fff;
|
||||
$surface-alt: #f8fafc;
|
||||
$border: #e2e8f0;
|
||||
$text-primary: #1e293b;
|
||||
$text-secondary: #64748b;
|
||||
$text-label: #475569;
|
||||
$accent-green: #059669;
|
||||
$accent-orange: #d97706;
|
||||
$radius-card: 12px;
|
||||
$radius-inner: 8px;
|
||||
$shadow-card:
|
||||
0 1px 3px rgb(0 0 0 / 7%),
|
||||
0 1px 2px rgb(0 0 0 / 4%);
|
||||
$shadow-hover: 0 4px 12px rgb(37 99 235 / 10%);
|
||||
|
||||
// ─── Card / Section ───────────────────────────────────────────────────────────
|
||||
.lpt-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
background: $surface;
|
||||
border: 1px solid $border;
|
||||
border-radius: $radius-card;
|
||||
box-shadow: $shadow-card;
|
||||
// No overflow:hidden here — it clips right-side content inside grids/tables.
|
||||
// Border-radius is preserved; header gets matching top-radius instead.
|
||||
overflow: visible;
|
||||
transition: box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-hover;
|
||||
}
|
||||
|
||||
& + & {
|
||||
margin-top: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,34 +49,52 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
padding: 14px 24px;
|
||||
background: linear-gradient(135deg, $surface-alt 0%, #f1f5f9 100%);
|
||||
border-bottom: 1px solid $border;
|
||||
border-radius: $radius-card $radius-card 0 0;
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
font-weight: 700;
|
||||
color: $text-primary;
|
||||
margin: 0;
|
||||
letter-spacing: 0.01em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.lpt-section-icon {
|
||||
color: #0ea5e9;
|
||||
color: $primary;
|
||||
font-size: 15px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $primary-light;
|
||||
border-radius: 6px;
|
||||
border: 1px solid $primary-border;
|
||||
}
|
||||
|
||||
.lpt-help-icon {
|
||||
color: #9aa0a6;
|
||||
color: $text-secondary;
|
||||
font-size: 13px;
|
||||
cursor: help;
|
||||
margin-left: auto;
|
||||
transition: color 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Grid layout ──────────────────────────────────────────────────────────────
|
||||
.lpt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
gap: 8px 20px;
|
||||
padding: 20px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -58,6 +104,10 @@
|
||||
.lpt-grid-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
@media (width <= 768px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -79,28 +129,42 @@
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
// ─── Checkbox rows ────────────────────────────────────────────────────────────
|
||||
.lpt-checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
padding: 6px 0;
|
||||
|
||||
.lpt-subsection {
|
||||
padding: 0 24px 16px;
|
||||
|
||||
&:first-of-type {
|
||||
padding-top: 0;
|
||||
mat-checkbox {
|
||||
font-size: 13.5px;
|
||||
color: $text-label;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Subsection ───────────────────────────────────────────────────────────────
|
||||
.lpt-subsection {
|
||||
padding: 0 24px 20px;
|
||||
}
|
||||
|
||||
.lpt-subsection-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #3c4043;
|
||||
margin: 8px 0 12px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
color: $text-secondary;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin: 12px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background: $primary;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.lpt-subsection-header {
|
||||
@@ -114,9 +178,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Misc ─────────────────────────────────────────────────────────────────────
|
||||
.lpt-table-wrapper {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid $border;
|
||||
border-radius: $radius-inner;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
|
||||
@@ -128,8 +193,265 @@
|
||||
.lpt-label-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 500;
|
||||
color: #3c4043;
|
||||
padding: 8px 0;
|
||||
color: $text-label;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
// ─── Interest Rate Slabs — Option C (2-column grid) ──────────────────────────
|
||||
.lpt-slab-c {
|
||||
padding: 16px 24px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
// Particular row
|
||||
.lpt-slab-c-particular {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 1px solid $border;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.lpt-slab-c-particular-label {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: $text-secondary;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lpt-slab-c-name-input {
|
||||
flex: 1;
|
||||
max-width: 320px;
|
||||
padding: 7px 12px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
background: $surface;
|
||||
outline: none;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
|
||||
&::placeholder {
|
||||
color: #b0b8c4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $primary;
|
||||
box-shadow: 0 0 0 3px rgb(37 99 235 / 10%);
|
||||
}
|
||||
}
|
||||
|
||||
// General / Special tab toggle
|
||||
.lpt-slab-tabs {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.lpt-slab-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 20px;
|
||||
background: $surface;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
color: $text-secondary;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
i {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $primary;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $primary;
|
||||
border-color: $primary;
|
||||
color: $surface;
|
||||
box-shadow: 0 2px 8px rgb(37 99 235 / 25%);
|
||||
}
|
||||
}
|
||||
|
||||
// 2-column slab grid
|
||||
.lpt-slab-c-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px 16px;
|
||||
|
||||
@media (width <= 540px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.lpt-slab-c-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
background: $surface;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: $primary;
|
||||
box-shadow: 0 0 0 3px rgb(37 99 235 / 8%);
|
||||
}
|
||||
}
|
||||
|
||||
// General + Special pair inside each card
|
||||
.lpt-slab-c-rate-pair {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lpt-slab-c-rate-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.lpt-slab-c-rate-label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: $primary;
|
||||
background: #eff6ff;
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
|
||||
&--special {
|
||||
color: #92400e;
|
||||
background: #fef9c3;
|
||||
}
|
||||
}
|
||||
|
||||
.lpt-slab-c-range {
|
||||
font-size: 12.5px;
|
||||
font-weight: 500;
|
||||
color: $text-label;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// Input + suffix wrapper
|
||||
.lpt-slab-c-input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.lpt-slab-c-input {
|
||||
width: 70px;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $text-primary;
|
||||
text-align: right;
|
||||
background: $surface-alt;
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
appearance: textfield;
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $primary;
|
||||
background: $primary-light;
|
||||
}
|
||||
|
||||
&--fixed {
|
||||
width: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
.lpt-slab-c-suffix {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: $text-secondary;
|
||||
user-select: none;
|
||||
|
||||
&--special {
|
||||
color: #92400e;
|
||||
}
|
||||
}
|
||||
|
||||
.lpt-slab-c-input--special {
|
||||
&:focus {
|
||||
border-color: #f59e0b;
|
||||
background: #fef9c3;
|
||||
}
|
||||
}
|
||||
|
||||
.lpt-slab-c-error {
|
||||
font-size: 10px;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
// Fixed Interest Rate row (below grid)
|
||||
.lpt-slab-c-fixed {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin: 12px -24px -20px;
|
||||
padding: 12px 16px;
|
||||
border-top: 2px dashed $primary-border;
|
||||
background: $primary-light;
|
||||
border-radius: 0 0 $radius-card $radius-card;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.lpt-slab-c-fixed-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $primary;
|
||||
user-select: none;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.lpt-slab-c-checkbox {
|
||||
accent-color: $primary;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
+82
-1
@@ -12,6 +12,9 @@ import {
|
||||
UntypedFormBuilder,
|
||||
Validators,
|
||||
UntypedFormControl,
|
||||
FormArray,
|
||||
FormGroup,
|
||||
FormControl,
|
||||
ReactiveFormsModule
|
||||
} from '@angular/forms';
|
||||
import { ProcessingStrategyService } from '../../services/processing-strategy.service';
|
||||
@@ -54,6 +57,25 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
||||
repaymentFrequencyTypeData: any;
|
||||
repaymentStartDateTypeOptions: any;
|
||||
|
||||
readonly interestSlabDefinitions = [
|
||||
{ label: 'Upto 10,000', maxAmount: 10000 },
|
||||
{ label: 'Upto 25,000', maxAmount: 25000 },
|
||||
{ label: 'Upto 1,00,000', maxAmount: 100000 },
|
||||
{ label: 'Upto 2,00,000', maxAmount: 200000 },
|
||||
{ label: 'Upto 5,00,000', maxAmount: 500000 },
|
||||
{ label: 'Upto 10,00,000', maxAmount: 1000000 },
|
||||
{ label: 'Upto 15,00,000', maxAmount: 1500000 },
|
||||
{ label: 'Upto 20,00,000', maxAmount: 2000000 },
|
||||
{ label: 'Upto 25,00,000', maxAmount: 2500000 },
|
||||
{ label: 'More than 25,00,000', maxAmount: null }
|
||||
];
|
||||
|
||||
interestRateSlabsArray: FormArray;
|
||||
specialSlabsArray: FormArray;
|
||||
fixedRateGroup: FormGroup;
|
||||
particularLabel = new FormControl('');
|
||||
activeSlabTab: 'general' | 'special' = 'general';
|
||||
|
||||
constructor() {
|
||||
this.createLoanProductTermsForm();
|
||||
this.setConditionalControls();
|
||||
@@ -109,6 +131,35 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
createLoanProductTermsForm() {
|
||||
this.interestRateSlabsArray = new FormArray(
|
||||
this.interestSlabDefinitions.map(
|
||||
() =>
|
||||
new FormGroup({
|
||||
interestRate: new FormControl(null, [
|
||||
Validators.min(0),
|
||||
Validators.max(100)
|
||||
])
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.specialSlabsArray = new FormArray(
|
||||
this.interestSlabDefinitions.map(
|
||||
() =>
|
||||
new FormGroup({
|
||||
interestRate: new FormControl(null, [
|
||||
Validators.min(0),
|
||||
Validators.max(100)
|
||||
])
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.fixedRateGroup = new FormGroup({
|
||||
hasFixed: new FormControl(false),
|
||||
fixedAmount: new FormControl({ value: null, disabled: true }, [Validators.min(0)])
|
||||
});
|
||||
|
||||
this.loanProductTermsForm = this.formBuilder.group({
|
||||
isLinkedToFloatingInterestRates: [false],
|
||||
allowApprovedDisbursedAmountsOverApplied: [false],
|
||||
@@ -230,6 +281,15 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
||||
}
|
||||
});
|
||||
|
||||
this.fixedRateGroup.get('hasFixed').valueChanges.subscribe((hasFixed: boolean) => {
|
||||
if (hasFixed) {
|
||||
this.fixedRateGroup.get('fixedAmount').enable();
|
||||
} else {
|
||||
this.fixedRateGroup.get('fixedAmount').disable();
|
||||
this.fixedRateGroup.get('fixedAmount').setValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
this.zeroInterest.valueChanges.subscribe((zeroInterest) => {
|
||||
if (zeroInterest) {
|
||||
this.loanProductTermsForm.get('minInterestRatePerPeriod').patchValue(0);
|
||||
@@ -256,6 +316,18 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
||||
});
|
||||
}
|
||||
|
||||
get slabGroups(): FormGroup[] {
|
||||
return this.interestRateSlabsArray.controls as FormGroup[];
|
||||
}
|
||||
|
||||
get activeSlabGroups(): FormGroup[] {
|
||||
return this.interestRateSlabsArray.controls as FormGroup[];
|
||||
}
|
||||
|
||||
get specialSlabGroups(): FormGroup[] {
|
||||
return this.specialSlabsArray.controls as FormGroup[];
|
||||
}
|
||||
|
||||
get loanProductTerms() {
|
||||
const formValue = this.loanProductTermsForm.getRawValue();
|
||||
// Normalize decimal separators: convert comma to dot for backend compatibility
|
||||
@@ -270,7 +342,16 @@ export class LoanProductTermsStepComponent implements OnInit, OnChanges {
|
||||
...formValue,
|
||||
minInterestRatePerPeriod: normalizeDecimal(formValue.minInterestRatePerPeriod),
|
||||
interestRatePerPeriod: normalizeDecimal(formValue.interestRatePerPeriod),
|
||||
maxInterestRatePerPeriod: normalizeDecimal(formValue.maxInterestRatePerPeriod)
|
||||
maxInterestRatePerPeriod: normalizeDecimal(formValue.maxInterestRatePerPeriod),
|
||||
interestRateSlabs: this.interestRateSlabsArray.getRawValue().map((slab, i) => ({
|
||||
maxAmount: this.interestSlabDefinitions[i].maxAmount,
|
||||
label: this.interestSlabDefinitions[i].label,
|
||||
interestRate: slab.interestRate
|
||||
})),
|
||||
fixedRate: (() => {
|
||||
const fr = this.fixedRateGroup.getRawValue();
|
||||
return fr.hasFixed ? fr.fixedAmount : null;
|
||||
})()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -564,6 +564,24 @@ export class ProductsService {
|
||||
return this.http.post('/fixeddepositproducts/baseinterestrates', payload);
|
||||
}
|
||||
|
||||
/** Returns loan interest rate categories. Pass activeOnly=true to get only active ones. */
|
||||
getLoanInterestRateCategories(activeOnly = false): Observable<any> {
|
||||
const url = activeOnly
|
||||
? '/loanproducts/interestratecategories?activeOnly=true'
|
||||
: '/loanproducts/interestratecategories';
|
||||
return this.http.get(url);
|
||||
}
|
||||
|
||||
/** Returns the active loan interest rate slab config (grid: loan type x amount range). */
|
||||
getLoanIrSlabs(): Observable<any> {
|
||||
return this.http.get('/loanproducts/interestrateslabs');
|
||||
}
|
||||
|
||||
/** Returns all loan interest rate slab config versions. */
|
||||
getAllLoanIrSlabs(): Observable<any> {
|
||||
return this.http.get('/loanproducts/interestrateslabs/all');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} Collateral data.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user