feat(WEB-505): implement waive penalties for loan repayment and reschedule (#2941)

Fix waive-penalties amount calc and align translations across locales

fix:test issue

Fix waive penalties recalculation, clean up subscription leaks, and align penalty translations
This commit is contained in:
Shubham Kumar
2025-12-28 16:09:08 -06:00
committed by GitHub
parent cb52f59dc3
commit 62c175ff15
21 changed files with 668 additions and 38 deletions
@@ -116,6 +116,42 @@
<input matInput formControlName="newInterestRate" /> <input matInput formControlName="newInterestRate" />
</mat-form-field> </mat-form-field>
} }
<mat-checkbox class="m-b-10" [formControl]="waivePenalties">
{{ 'labels.inputs.Waive Penalties' | translate }}
</mat-checkbox>
@if (waivePenalties.value) {
@if (penalties.length > 0) {
<div class="layout-row-wrap m-t-20">
<div class="flex-100 m-b-10">
<mat-checkbox [checked]="selectAllPenalties" (change)="toggleSelectAllPenalties()">
{{ 'labels.inputs.All' | translate }}
</mat-checkbox>
</div>
<div class="flex-100 layout-row-wrap">
@for (penalty of penalties; track penalty.id) {
<div class="penalty-item-container">
<mat-checkbox
[checked]="isPenaltySelected(penalty.id)"
(change)="togglePenaltySelection(penalty.id)"
></mat-checkbox>
<span class="penalty-label mat-body-strong">{{ 'labels.inputs.Penalty' | translate }}</span>
<span class="penalty-name">{{ getPenaltyDisplayKey(penalty) | translate }}</span>
<span class="penalty-date">{{ penalty.dueDate | dateFormat }}</span>
<span class="penalty-amount r-amount">{{
penalty.amountOutstanding || penalty.amount || 0 | formatNumber
}}</span>
</div>
}
</div>
</div>
} @else {
<div class="m-t-20 m-l-10">
<span class="mat-body-2">{{ 'labels.text.No penalties found' | translate }}</span>
</div>
}
}
</div> </div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column"> <mat-card-actions class="layout-row align-center gap-5px responsive-column">
@@ -1,3 +1,45 @@
.container { .container {
max-width: 37rem; max-width: 37rem;
} }
.penalty-item-container {
width: 100%;
max-width: 100%;
display: grid;
grid-template-columns: auto minmax(80px, 110px) minmax(140px, 100%) minmax(130px, 170px) minmax(90px, 120px);
align-items: center;
gap: 8px;
margin-top: 10px;
padding: 12px 16px;
background-color: #f5f5f5;
border: 1px solid #e0e0e0;
border-radius: 4px;
box-sizing: border-box;
overflow: hidden;
}
.penalty-label {
min-width: 80px;
}
.penalty-name {
min-width: 120px;
}
.penalty-date {
color: rgb(0 0 0 / 70%);
white-space: nowrap;
}
.penalty-amount {
text-align: right;
white-space: nowrap;
}
:host-context(.dark-theme) {
.penalty-item-container {
background-color: #3a3a3a;
color: rgb(255 255 255 / 87%);
border-color: #4a4a4a;
}
}
@@ -1,4 +1,4 @@
import { Component, OnInit, Input, inject } from '@angular/core'; import { Component, OnInit, Input, inject, DestroyRef } from '@angular/core';
import { LoansService } from 'app/loans/loans.service'; import { LoansService } from 'app/loans/loans.service';
import { import {
UntypedFormBuilder, UntypedFormBuilder,
@@ -13,7 +13,10 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { SettingsService } from 'app/settings/settings.service'; import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates'; import { Dates } from 'app/core/utils/dates';
import { MatCheckbox } from '@angular/material/checkbox'; import { MatCheckbox } from '@angular/material/checkbox';
import { PenaltyManagementService } from 'app/loans/services/penalty-management.service';
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({ @Component({
selector: 'mifosx-loan-reschedule', selector: 'mifosx-loan-reschedule',
@@ -21,7 +24,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./loan-reschedule.component.scss'], styleUrls: ['./loan-reschedule.component.scss'],
imports: [ imports: [
...STANDALONE_SHARED_IMPORTS, ...STANDALONE_SHARED_IMPORTS,
MatCheckbox MatCheckbox,
FormatNumberPipe
] ]
}) })
export class LoanRescheduleComponent implements OnInit { export class LoanRescheduleComponent implements OnInit {
@@ -31,6 +35,8 @@ export class LoanRescheduleComponent implements OnInit {
private router = inject(Router); private router = inject(Router);
private dateUtils = inject(Dates); private dateUtils = inject(Dates);
private settingsService = inject(SettingsService); private settingsService = inject(SettingsService);
private penaltyManagementService = inject(PenaltyManagementService);
private destroyRef = inject(DestroyRef);
@Input() dataObject: any; @Input() dataObject: any;
loanId: any; loanId: any;
@@ -46,6 +52,14 @@ export class LoanRescheduleComponent implements OnInit {
introduceGracePeriods = new UntypedFormControl(false); introduceGracePeriods = new UntypedFormControl(false);
extendRepaymentPeriod = new UntypedFormControl(false); extendRepaymentPeriod = new UntypedFormControl(false);
adjustinterestrates = new UntypedFormControl(false); adjustinterestrates = new UntypedFormControl(false);
waivePenalties = new UntypedFormControl(false);
/** Penalties list */
penalties: any[] = [];
/** Selected penalty IDs */
selectedPenalties: number[] = [];
/** Select all penalties checkbox */
selectAllPenalties = false;
/** /**
* @param {FormBuilder} formBuilder Form Builder. * @param {FormBuilder} formBuilder Form Builder.
@@ -62,6 +76,8 @@ export class LoanRescheduleComponent implements OnInit {
this.maxDate = this.settingsService.businessDate; this.maxDate = this.settingsService.businessDate;
this.codes = this.dataObject.rescheduleReasons; this.codes = this.dataObject.rescheduleReasons;
this.setRescheduleLoanForm(); this.setRescheduleLoanForm();
this.loadPenalties();
this.setupWaivePenaltiesListener();
} }
setRescheduleLoanForm() { setRescheduleLoanForm() {
@@ -109,6 +125,12 @@ export class LoanRescheduleComponent implements OnInit {
locale locale
}; };
data.loanId = this.loanId; data.loanId = this.loanId;
// Add waived penalty charge IDs if penalties are being waived
if (this.waivePenalties.value && this.selectedPenalties.length > 0) {
data.chargeIds = this.selectedPenalties;
}
this.loanService.submitRescheduleData(data).subscribe((response: any) => { this.loanService.submitRescheduleData(data).subscribe((response: any) => {
// TODO: needs to be updated // TODO: needs to be updated
// mentioned in Community App: // mentioned in Community App:
@@ -116,4 +138,74 @@ export class LoanRescheduleComponent implements OnInit {
this.router.navigate(['../../general'], { relativeTo: this.route }); this.router.navigate(['../../general'], { relativeTo: this.route });
}); });
} }
/**
* Load penalties for the loan
* Penalties are charges calculated for installments in the payment schedule.
* Each penalty charge has a dueDate that corresponds to an installment due date.
*/
loadPenalties() {
this.penaltyManagementService.loadPenalties(this.loanId).subscribe({
next: (penalties: any[]) => {
this.penalties = penalties;
},
error: (error: any) => {
console.error('Error loading penalties:', error);
this.penalties = [];
}
});
}
/**
* Setup listener for waive penalties checkbox
* Following the pattern of using valueChanges.subscribe()
*/
setupWaivePenaltiesListener() {
this.waivePenalties.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value: boolean) => {
if (!value) {
// Reset selections when toggling off
this.selectedPenalties = [];
this.selectAllPenalties = false;
}
});
}
/**
* Toggle select all penalties
* Following the toggleSelects() pattern from loans-active-client-members
*/
toggleSelectAllPenalties() {
const result = this.penaltyManagementService.toggleSelectAllPenalties(this.selectAllPenalties, this.penalties);
this.selectAllPenalties = result.selectAllPenalties;
this.selectedPenalties = result.selectedPenalties;
}
/**
* Toggle individual penalty selection
* Following the toggleSelect() pattern from loans-active-client-members
*/
togglePenaltySelection(penaltyId: number) {
const result = this.penaltyManagementService.togglePenaltySelection(
penaltyId,
this.selectedPenalties,
this.penalties
);
this.selectedPenalties = result.selectedPenalties;
this.selectAllPenalties = result.selectAllPenalties;
}
/**
* Check if penalty is selected
*/
isPenaltySelected(penaltyId: number): boolean {
return this.penaltyManagementService.isPenaltySelected(penaltyId, this.selectedPenalties);
}
/**
* Get penalty display key or plain text for translation/output
* Normalizes common backend values (like MORA / labels.inputs.*) to translation keys
*/
getPenaltyDisplayKey(penalty: any): string {
return this.penaltyManagementService.getPenaltyDisplayKey(penalty);
}
} }
@@ -89,16 +89,50 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div class="flex-fill"> <div class="flex-fill m-b-10">
<span class="expandcollapsebutton m-l-10 m-t-40 flex-75" (click)="addPaymentDetails()"> <mat-slide-toggle [checked]="showPaymentDetails" (change)="addPaymentDetails()">
<mat-slide-toggle> {{ 'labels.inputs.Show Payment Details' | translate }}
<div [className]="showPaymentDetails ? 'enabled' : 'disabled'"> </mat-slide-toggle>
<span class="m-l-10">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
</div>
</mat-slide-toggle>
</span>
</div> </div>
<div class="flex-fill m-b-10">
<mat-slide-toggle [checked]="waivePenalties" (change)="toggleWaivePenalties()">
{{ 'labels.inputs.Waive Penalties' | translate }}
</mat-slide-toggle>
</div>
@if (waivePenalties) {
@if (penalties.length > 0) {
<div class="layout-row-wrap m-t-20">
<div class="flex-100 m-b-10">
<mat-checkbox [checked]="selectAllPenalties" (change)="toggleSelectAllPenalties()">
{{ 'labels.inputs.All' | translate }}
</mat-checkbox>
</div>
<div class="flex-100 layout-row-wrap">
@for (penalty of penalties; track penalty.id) {
<div class="penalty-item-container">
<mat-checkbox
[checked]="isPenaltySelected(penalty.id)"
(change)="togglePenaltySelection(penalty.id)"
></mat-checkbox>
<span class="penalty-label mat-body-strong">{{ 'labels.inputs.Penalty' | translate }}</span>
<span class="penalty-name">{{ getPenaltyDisplayKey(penalty) | translate }}</span>
<span class="penalty-date">{{ penalty.dueDate | dateFormat }}</span>
<span class="penalty-amount r-amount">{{
penalty.amountOutstanding || penalty.amount || 0 | formatNumber
}}</span>
</div>
}
</div>
</div>
} @else {
<div class="m-t-20 m-l-10">
<span class="mat-body-2">{{ 'labels.text.No penalties found' | translate }}</span>
</div>
}
}
@if (showPaymentDetails) { @if (showPaymentDetails) {
<mat-form-field> <mat-form-field>
<mat-label> {{ 'labels.inputs.Account' | translate }} #</mat-label> <mat-label> {{ 'labels.inputs.Account' | translate }} #</mat-label>
@@ -13,3 +13,45 @@
.right-label { .right-label {
padding-right: 25px !important; padding-right: 25px !important;
} }
.penalty-item-container {
width: 100%;
max-width: 100%;
display: grid;
grid-template-columns: auto minmax(80px, 110px) minmax(140px, 100%) minmax(130px, 170px) minmax(90px, 120px);
align-items: center;
gap: 8px;
margin-top: 10px;
padding: 12px 16px;
background-color: #f5f5f5;
border: 1px solid #e0e0e0;
border-radius: 4px;
box-sizing: border-box;
overflow: hidden;
}
.penalty-label {
min-width: 80px;
}
.penalty-name {
min-width: 120px;
}
.penalty-date {
white-space: nowrap;
}
.penalty-amount {
text-align: right;
white-space: nowrap;
}
// Dark mode styles
:host-context(.dark-theme) {
.penalty-item-container {
background-color: #3a3a3a;
color: rgb(255 255 255 / 87%);
border-color: #4a4a4a;
}
}
@@ -8,8 +8,10 @@ import { LoansService } from 'app/loans/loans.service';
import { SettingsService } from 'app/settings/settings.service'; import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates'; import { Dates } from 'app/core/utils/dates';
import { Currency } from 'app/shared/models/general.model'; import { Currency } from 'app/shared/models/general.model';
import { PenaltyManagementService } from 'app/loans/services/penalty-management.service';
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component'; import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
import { MatSlideToggle } from '@angular/material/slide-toggle'; import { MatSlideToggle } from '@angular/material/slide-toggle';
import { MatCheckbox } from '@angular/material/checkbox';
import { CdkTextareaAutosize } from '@angular/cdk/text-field'; import { CdkTextareaAutosize } from '@angular/cdk/text-field';
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe'; import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -25,6 +27,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS, ...STANDALONE_SHARED_IMPORTS,
InputAmountComponent, InputAmountComponent,
MatSlideToggle, MatSlideToggle,
MatCheckbox,
CdkTextareaAutosize, CdkTextareaAutosize,
FormatNumberPipe FormatNumberPipe
] ]
@@ -36,6 +39,7 @@ export class MakeRepaymentComponent implements OnInit {
private router = inject(Router); private router = inject(Router);
private dateUtils = inject(Dates); private dateUtils = inject(Dates);
private settingsService = inject(SettingsService); private settingsService = inject(SettingsService);
private penaltyManagementService = inject(PenaltyManagementService);
@Input() dataObject: any; @Input() dataObject: any;
/** Loan Id */ /** Loan Id */
@@ -44,6 +48,14 @@ export class MakeRepaymentComponent implements OnInit {
paymentTypes: any; paymentTypes: any;
/** Show payment details */ /** Show payment details */
showPaymentDetails = false; showPaymentDetails = false;
/** Waive Penalties toggle */
waivePenalties = false;
/** Penalties list */
penalties: any[] = [];
/** Selected penalty IDs */
selectedPenalties: number[] = [];
/** Select all penalties checkbox */
selectAllPenalties = false;
/** Minimum Date allowed. */ /** Minimum Date allowed. */
minDate = new Date(2000, 0, 1); minDate = new Date(2000, 0, 1);
/** Maximum Date allowed. */ /** Maximum Date allowed. */
@@ -79,6 +91,7 @@ export class MakeRepaymentComponent implements OnInit {
if (this.dataObject.currency) { if (this.dataObject.currency) {
this.currency = this.dataObject.currency; this.currency = this.dataObject.currency;
} }
this.loadPenalties();
} }
/** /**
@@ -97,22 +110,11 @@ export class MakeRepaymentComponent implements OnInit {
}); });
if (this.isCapitalizedIncome()) { if (this.isCapitalizedIncome()) {
this.repaymentLoanForm.addControl( this.repaymentLoanForm.addControl('transactionAmount', new UntypedFormControl('', []));
'transactionAmount', this.updateTransactionAmountValidators(false);
new UntypedFormControl('', [
Validators.required,
Validators.min(0.001),
Validators.max(this.dataObject.amount)
])
);
} else { } else {
this.repaymentLoanForm.addControl( this.repaymentLoanForm.addControl('transactionAmount', new UntypedFormControl('', []));
'transactionAmount', this.updateTransactionAmountValidators(false);
new UntypedFormControl('', [
Validators.required,
Validators.min(0.001)
])
);
} }
if (this.isCapitalizedIncome() || this.isBuyDownFee()) { if (this.isCapitalizedIncome() || this.isBuyDownFee()) {
this.repaymentLoanForm.addControl('classificationId', new UntypedFormControl('')); this.repaymentLoanForm.addControl('classificationId', new UntypedFormControl(''));
@@ -169,6 +171,141 @@ export class MakeRepaymentComponent implements OnInit {
return code.includes('merchantissuedrefund') || code.includes('payoutrefund'); return code.includes('merchantissuedrefund') || code.includes('payoutrefund');
} }
/**
* Load penalties for the loan
* Penalties are charges calculated for installments in the payment schedule.
* Each penalty charge has a dueDate that corresponds to an installment due date.
*/
loadPenalties() {
this.penaltyManagementService.loadPenalties(this.loanId).subscribe({
next: (penalties: any[]) => {
this.penalties = penalties;
},
error: (error: any) => {
console.error('Error loading penalties:', error);
this.penalties = [];
}
});
}
/**
* Toggle waive penalties
* Following the addPaymentDetails() pattern
*/
toggleWaivePenalties() {
this.waivePenalties = !this.waivePenalties;
if (!this.waivePenalties) {
// Reset selections when toggling off
this.selectedPenalties = [];
this.selectAllPenalties = false;
this.updateTransactionAmountValidators(false);
this.recalculateTransactionAmount();
} else {
this.recalculateTransactionAmount();
}
}
/**
* Toggle select all penalties
* Following the toggleSelects() pattern from loans-active-client-members
*/
toggleSelectAllPenalties() {
const result = this.penaltyManagementService.toggleSelectAllPenalties(this.selectAllPenalties, this.penalties);
this.selectAllPenalties = result.selectAllPenalties;
this.selectedPenalties = result.selectedPenalties;
this.recalculateTransactionAmount();
}
/**
* Toggle individual penalty selection
* Following the toggleSelect() pattern from loans-active-client-members
*/
togglePenaltySelection(penaltyId: number) {
const result = this.penaltyManagementService.togglePenaltySelection(
penaltyId,
this.selectedPenalties,
this.penalties
);
this.selectedPenalties = result.selectedPenalties;
this.selectAllPenalties = result.selectAllPenalties;
this.recalculateTransactionAmount();
}
/**
* Check if penalty is selected
*/
isPenaltySelected(penaltyId: number): boolean {
return this.penaltyManagementService.isPenaltySelected(penaltyId, this.selectedPenalties);
}
/**
* Get penalty display key or plain text for translation/output
* Normalizes common backend values (like MORA / labels.inputs.*) to translation keys
*/
getPenaltyDisplayKey(penalty: any): string {
return this.penaltyManagementService.getPenaltyDisplayKey(penalty);
}
/**
* Recalculate transaction amount when penalties are waived
*/
recalculateTransactionAmount() {
const currentAmountValue = this.repaymentLoanForm.value.transactionAmount;
const currentAmount =
currentAmountValue !== undefined && currentAmountValue !== null
? Number(currentAmountValue)
: Number(this.dataObject.amount ?? 0);
const baseAmount = isNaN(currentAmount) ? Number(this.dataObject.amount ?? 0) : currentAmount;
if (!this.waivePenalties || this.selectedPenalties.length === 0) {
// Reset to original amount if no penalties selected
this.repaymentLoanForm.patchValue(
{
transactionAmount: baseAmount
},
{ emitEvent: false }
);
return;
}
// Calculate total waived amount
let totalWaived = 0;
this.selectedPenalties.forEach((penaltyId: number) => {
const penalty = this.penalties.find((p: any) => p.id === penaltyId);
if (penalty) {
totalWaived += penalty.amountOutstanding || penalty.amount || 0;
}
});
// Calculate new transaction amount
const newAmount = Math.max(0, baseAmount - totalWaived);
// Allow zero when fully waived
this.updateTransactionAmountValidators(this.waivePenalties && newAmount === 0);
this.repaymentLoanForm.patchValue(
{
transactionAmount: newAmount
},
{ emitEvent: false }
);
}
/**
* Update transaction amount validators to allow or disallow zero
*/
private updateTransactionAmountValidators(allowZero: boolean) {
const validators = [
Validators.required,
Validators.min(allowZero ? 0 : 0.001)
];
if (this.isCapitalizedIncome()) {
validators.push(Validators.max(this.dataObject.amount));
}
this.repaymentLoanForm.controls.transactionAmount.setValidators(validators);
this.repaymentLoanForm.controls.transactionAmount.updateValueAndValidity({ emitEvent: false });
}
/** Submits the repayment form */ /** Submits the repayment form */
submit() { submit() {
const repaymentLoanFormData = this.repaymentLoanForm.value; const repaymentLoanFormData = this.repaymentLoanForm.value;
@@ -188,6 +325,12 @@ export class MakeRepaymentComponent implements OnInit {
data.interestRefundCalculation = false; data.interestRefundCalculation = false;
} }
delete data.skipInterestRefund; delete data.skipInterestRefund;
// Add waived penalty charge IDs if penalties are being waived
if (this.waivePenalties && this.selectedPenalties.length > 0) {
data.chargeIds = this.selectedPenalties;
}
this.loanService.submitLoanActionButton(this.loanId, data, this.command).subscribe((response: any) => { this.loanService.submitLoanActionButton(this.loanId, data, this.command).subscribe((response: any) => {
this.router.navigate(['../../transactions'], { relativeTo: this.route }); this.router.navigate(['../../transactions'], { relativeTo: this.route });
}); });
+8
View File
@@ -528,6 +528,14 @@ export class LoansService {
return this.http.get(`/loans/${loanId}/guarantors/accounts/template`, { params: httpParams }); return this.http.get(`/loans/${loanId}/guarantors/accounts/template`, { params: httpParams });
} }
/**
* @param {string} loanId Loan Id
* @returns {Observable<any>} All charges for the loan
*/
getLoanCharges(loanId: string): Observable<any> {
return this.http.get(`/loans/${loanId}/charges`);
}
/** /**
* @param {string} accountId loans account Id * @param {string} accountId loans account Id
* @param {string} chargeId loans charge Id * @param {string} chargeId loans charge Id
@@ -0,0 +1,190 @@
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { LoansService } from '../loans.service';
import { Dates } from 'app/core/utils/dates';
/**
* Service for managing loan penalty charges
* Provides shared logic for loading, filtering, and managing penalties
*/
@Injectable({
providedIn: 'root'
})
export class PenaltyManagementService {
private loanService = inject(LoansService);
private dateUtils = inject(Dates);
/**
* Load penalties for the loan
* Penalties are charges calculated for installments in the payment schedule.
* Each penalty charge has a dueDate that corresponds to an installment due date.
* @param loanId The loan ID
* @returns Observable of filtered and sorted penalties
*/
loadPenalties(loanId: string): Observable<any[]> {
return this.loanService.getLoanCharges(loanId).pipe(
map((response: any) => {
// Handle different response structures - could be array directly or wrapped
let charges: any[] = [];
if (Array.isArray(response)) {
charges = response;
} else if (response && Array.isArray(response.pageItems)) {
charges = response.pageItems;
} else if (response && Array.isArray(response.content)) {
charges = response.content;
} else if (response && Array.isArray(response.charges)) {
charges = response.charges;
} else if (response && response.data && Array.isArray(response.data)) {
charges = response.data;
}
const filteredPenalties = this.filterPenaltyCharges(charges);
// Parse dates for proper display
// The dueDate on each penalty corresponds to the installment due date in the payment schedule
filteredPenalties.forEach((penalty: any) => {
if (penalty.dueDate) {
penalty.dueDate = this.dateUtils.parseDate(penalty.dueDate);
}
});
// Sort by due date to show penalties in chronological order
filteredPenalties.sort((a: any, b: any) => {
if (a.dueDate && b.dueDate) {
return a.dueDate.getTime() - b.dueDate.getTime();
}
return 0;
});
return filteredPenalties;
})
);
}
/**
* Filter penalty charges from all charges
* Returns only penalty charges that are:
* - Marked as penalties (penalty === true) or overdue charges
* - Not already waived or paid
* - Have outstanding amounts
* These penalties are calculated for specific installments in the payment schedule.
* @param charges Array of all charges
* @returns Array of filtered penalty charges
*/
filterPenaltyCharges(charges: any[]): any[] {
if (!charges || !Array.isArray(charges)) {
return [];
}
return charges.filter((charge: any) => {
if (!charge) {
return false;
}
// Check if it's a penalty charge
const isPenalty =
charge.penalty === true ||
charge.penalty === 'true' ||
(charge.chargeTimeType &&
(charge.chargeTimeType.value?.toLowerCase().includes('overdue') ||
charge.chargeTimeType.code?.toLowerCase().includes('overdue') ||
charge.chargeTimeType.id === 9)); // Overdue charge time type ID
if (!isPenalty) {
return false;
}
// Check if already waived or paid
if (charge.waived === true || charge.waived === 'true' || charge.paid === true || charge.paid === 'true') {
return false;
}
// Check if has outstanding amount
const outstanding =
charge.amountOutstanding || charge.amountOutstanding === 0 ? charge.amountOutstanding : charge.amount;
return outstanding > 0;
});
}
/**
* Toggle select all penalties
* @param selectAllPenalties Current state of select all checkbox
* @param penalties Array of all penalties
* @returns Object with updated selectAllPenalties state and selectedPenalties array
*/
toggleSelectAllPenalties(
selectAllPenalties: boolean,
penalties: any[]
): { selectAllPenalties: boolean; selectedPenalties: number[] } {
const newSelectAll = !selectAllPenalties;
return {
selectAllPenalties: newSelectAll,
selectedPenalties: newSelectAll ? penalties.map((penalty: any) => penalty.id) : []
};
}
/**
* Toggle individual penalty selection
* @param penaltyId The penalty ID to toggle
* @param selectedPenalties Current array of selected penalty IDs
* @param penalties Array of all penalties
* @returns Object with updated selectedPenalties array and selectAllPenalties state
*/
togglePenaltySelection(
penaltyId: number,
selectedPenalties: number[],
penalties: any[]
): { selectedPenalties: number[]; selectAllPenalties: boolean } {
const newSelectedPenalties = [...selectedPenalties];
const index = newSelectedPenalties.indexOf(penaltyId);
if (index > -1) {
newSelectedPenalties.splice(index, 1);
} else {
newSelectedPenalties.push(penaltyId);
}
// Update select all checkbox state
const len = penalties.length;
const selectAllPenalties = len === 0 ? false : newSelectedPenalties.length === len;
return {
selectedPenalties: newSelectedPenalties,
selectAllPenalties
};
}
/**
* Check if penalty is selected
* @param penaltyId The penalty ID to check
* @param selectedPenalties Array of selected penalty IDs
* @returns True if penalty is selected
*/
isPenaltySelected(penaltyId: number, selectedPenalties: number[]): boolean {
return selectedPenalties.includes(penaltyId);
}
/**
* Get penalty display key or plain text for translation/output
* Normalizes common backend values (like MORA / labels.inputs.*) to translation keys
* @param penalty The penalty object
* @returns Translation key or plain text
*/
getPenaltyDisplayKey(penalty: any): string {
if (!penalty) {
return 'labels.inputs.Overdue Fees';
}
const rawName = (penalty.name || penalty.chargeTimeType?.value || 'Overdue Fees').toString().trim();
const upperName = rawName.toUpperCase();
// Handle known aliases from backend
if (upperName === 'MORA' || upperName === 'OVERDUE') {
return 'labels.inputs.Overdue';
}
if (upperName === 'OVERDUE FEES') {
return 'labels.inputs.Overdue Fees';
}
// Pass through existing translation keys (labels.*)
if (rawName.startsWith('labels.')) {
return rawName;
}
// Fall back to the raw name (translate pipe will no-op if not a key)
return rawName;
}
}
+6 -1
View File
@@ -1106,6 +1106,7 @@
"Offices": "Kanceláře", "Offices": "Kanceláře",
"Options": "Možnosti", "Options": "Možnosti",
"Other Details": "Další detaily", "Other Details": "Další detaily",
"Overdue": "Po splatnosti",
"Overdue Charges": "Poplatky po splatnosti", "Overdue Charges": "Poplatky po splatnosti",
"Password Preferences": "Předvolby hesla", "Password Preferences": "Předvolby hesla",
"Payment Type": "Způsob platby", "Payment Type": "Způsob platby",
@@ -2358,6 +2359,7 @@
"Show Closed Centers": "Zobrazit uzavřená centra", "Show Closed Centers": "Zobrazit uzavřená centra",
"Show Closed Groups": "Zobrazit uzavřené skupiny", "Show Closed Groups": "Zobrazit uzavřené skupiny",
"Show Payment Details": "Zobrazit podrobnosti o platbě", "Show Payment Details": "Zobrazit podrobnosti o platbě",
"Waive Penalties": "Zřeknutí se pokut",
"Slab": "Pásmo", "Slab": "Pásmo",
"Specific to Installment": "Specifické pro splátky", "Specific to Installment": "Specifické pro splátky",
"Staff": "Personál", "Staff": "Personál",
@@ -2583,7 +2585,9 @@
"Income from Buy down fees": "Příjmy z poplatků za odkup", "Income from Buy down fees": "Příjmy z poplatků za odkup",
"Buy down fee Expense": "Náklady na poplatek za odkup", "Buy down fee Expense": "Náklady na poplatek za odkup",
"Skip Interest Refund Transaction Posting": "Přeskočit zaúčtování vrácení úroků", "Skip Interest Refund Transaction Posting": "Přeskočit zaúčtování vrácení úroků",
"Available Disbursement Amount (with Over Applied)": "Dostupná částka k výplatě (s přeplatkem)" "Available Disbursement Amount (with Over Applied)": "Dostupná částka k výplatě (s přeplatkem)",
"Overdue": "Po splatnosti",
"Overdue Fees": "Poplatky po splatnosti"
}, },
"links": { "links": {
"Community": "Společenství", "Community": "Společenství",
@@ -3167,6 +3171,7 @@
"No checker inbox data available for this search": "Pro toto vyhledávání nejsou k dispozici žádná data doručené pošty Checker.", "No checker inbox data available for this search": "Pro toto vyhledávání nejsou k dispozici žádná data doručené pošty Checker.",
"No client was found": "Nebyl nalezen žádný klient", "No client was found": "Nebyl nalezen žádný klient",
"No data found": "Nenalezena žádná data", "No data found": "Nenalezena žádná data",
"No penalties found": "Nebyly nalezeny žádné pokuty",
"NoDocuments": "Žádné dokumenty k dispozici.", "NoDocuments": "Žádné dokumenty k dispozici.",
"NoFileSelected": "Nevybrán žádný soubor", "NoFileSelected": "Nevybrán žádný soubor",
"NoNotesAvailable": "Žádné poznámky k dispozici", "NoNotesAvailable": "Žádné poznámky k dispozici",
+7 -4
View File
@@ -1107,6 +1107,7 @@
"Offices": "Büros", "Offices": "Büros",
"Options": "Optionen", "Options": "Optionen",
"Other Details": "Andere Details", "Other Details": "Andere Details",
"Overdue": "Überfällig",
"Overdue Charges": "Überfällige Gebühren", "Overdue Charges": "Überfällige Gebühren",
"Password Preferences": "Passworteinstellungen", "Password Preferences": "Passworteinstellungen",
"Payment Type": "Zahlungsart", "Payment Type": "Zahlungsart",
@@ -2358,6 +2359,7 @@
"Show Closed Centers": "Geschlossene Zentren anzeigen", "Show Closed Centers": "Geschlossene Zentren anzeigen",
"Show Closed Groups": "Geschlossene Gruppen anzeigen", "Show Closed Groups": "Geschlossene Gruppen anzeigen",
"Show Payment Details": "Zahlungsdetails anzeigen", "Show Payment Details": "Zahlungsdetails anzeigen",
"Waive Penalties": "Auf Strafen verzichten",
"Slab": "Stufe", "Slab": "Stufe",
"Specific to Installment": "Speziell für Ratenzahlung", "Specific to Installment": "Speziell für Ratenzahlung",
"Staff": "Personal", "Staff": "Personal",
@@ -2583,7 +2585,9 @@
"Income from Buy down fees": "Einnahmen aus Buy-Down-Gebühren", "Income from Buy down fees": "Einnahmen aus Buy-Down-Gebühren",
"Buy down fee Expense": "Aufwendungen aus Buy-Down-Gebühren", "Buy down fee Expense": "Aufwendungen aus Buy-Down-Gebühren",
"Skip Interest Refund Transaction Posting": "Buchung der Zinsrückerstattung überspringen", "Skip Interest Refund Transaction Posting": "Buchung der Zinsrückerstattung überspringen",
"Available Disbursement Amount (with Over Applied)": "Verfügbarer Auszahlungsbetrag (mit Überzahlung)" "Available Disbursement Amount (with Over Applied)": "Verfügbarer Auszahlungsbetrag (mit Überzahlung)",
"Overdue": "Überfällig",
"Overdue Fees": "Überfällige Gebühren"
}, },
"links": { "links": {
"Community": "Gemeinschaft", "Community": "Gemeinschaft",
@@ -2799,8 +2803,7 @@
"All Recurring Deposits": "Alle wiederkehrenden Einzahlungen", "All Recurring Deposits": "Alle wiederkehrenden Einzahlungen",
"All Savings": "Alle Einsparungen", "All Savings": "Alle Einsparungen",
"Savings Account Details": "Sparkonto-Details", "Savings Account Details": "Sparkonto-Details",
"Create Savings Product": "Sparkonto anlegen", "Create Savings Product": "Sparprodukt erstellen",
"Create Savings Product": "Sparkonto anlegen",
"Allocate Cash": "Bargeld zuweisen", "Allocate Cash": "Bargeld zuweisen",
"Allows you to create new fixed deposit product": "Mit dieser Option können Sie ein neues Festgeldprodukt erstellen.", "Allows you to create new fixed deposit product": "Mit dieser Option können Sie ein neues Festgeldprodukt erstellen.",
"This option allows you to create new users in your organization": "Mit dieser Option können Sie neue Benutzer in Ihrer Organisation erstellen", "This option allows you to create new users in your organization": "Mit dieser Option können Sie neue Benutzer in Ihrer Organisation erstellen",
@@ -2907,7 +2910,6 @@
"Create Recurring Deposits Account": "Erstellen Sie ein Konto für wiederkehrende Einlagen", "Create Recurring Deposits Account": "Erstellen Sie ein Konto für wiederkehrende Einlagen",
"Create Report": "Bericht erstellen", "Create Report": "Bericht erstellen",
"Create SMS Campaign": "Erstellen Sie eine SMS-Kampagne", "Create SMS Campaign": "Erstellen Sie eine SMS-Kampagne",
"Create Savings Product": "Sparprodukt erstellen",
"Create Savings Account": "Sparkonto erstellen", "Create Savings Account": "Sparkonto erstellen",
"Create Self Service User": "Erstellen Sie einen Self-Service-Benutzer", "Create Self Service User": "Erstellen Sie einen Self-Service-Benutzer",
"Create Share Product": "Share-Produkt erstellen", "Create Share Product": "Share-Produkt erstellen",
@@ -3168,6 +3170,7 @@
"No checker inbox data available for this search": "Für diese Suche sind keine Checker-Posteingangsdaten verfügbar.", "No checker inbox data available for this search": "Für diese Suche sind keine Checker-Posteingangsdaten verfügbar.",
"No client was found": "Es wurde kein Kunde gefunden", "No client was found": "Es wurde kein Kunde gefunden",
"No data found": "Keine Daten gefunden", "No data found": "Keine Daten gefunden",
"No penalties found": "Keine Strafen gefunden",
"NoDocuments": "Keine Dokumente verfügbar.", "NoDocuments": "Keine Dokumente verfügbar.",
"NoFileSelected": "Keine Datei ausgewählt", "NoFileSelected": "Keine Datei ausgewählt",
"NoNotesAvailable": "Keine Notizen verfügbar", "NoNotesAvailable": "Keine Notizen verfügbar",
+6 -3
View File
@@ -1112,6 +1112,7 @@
"Offices": "Offices", "Offices": "Offices",
"Options": "Options", "Options": "Options",
"Other Details": "Other Details", "Other Details": "Other Details",
"Overdue": "Overdue",
"Overdue Charges": "Overdue Charges", "Overdue Charges": "Overdue Charges",
"Password Preferences": "Password Preferences", "Password Preferences": "Password Preferences",
"Payment Type": "Payment Type", "Payment Type": "Payment Type",
@@ -2591,7 +2592,10 @@
"Income from Buy down fees": "Income from Buy down fees", "Income from Buy down fees": "Income from Buy down fees",
"Buy down fee Expense": "Buy down fee Expense", "Buy down fee Expense": "Buy down fee Expense",
"Skip Interest Refund Transaction Posting": "Skip Interest Refund Transaction Posting", "Skip Interest Refund Transaction Posting": "Skip Interest Refund Transaction Posting",
"Available Disbursement Amount (with Over Applied)": "Available Disbursement Amount (with Over Applied)" "Available Disbursement Amount (with Over Applied)": "Available Disbursement Amount (with Over Applied)",
"Waive Penalties": "Waive Penalties",
"Overdue": "Overdue",
"Overdue Fees": "Overdue Fees"
}, },
"links": { "links": {
"Community": "Community", "Community": "Community",
@@ -3532,8 +3536,7 @@
"“Maker-Checker” principle requires every tasks": "The “Maker-Checker” principle requires every tasks to be completed by two people to reduce the chance of errors and misuse. One person initiates the process and the second completes it.", "“Maker-Checker” principle requires every tasks": "The “Maker-Checker” principle requires every tasks to be completed by two people to reduce the chance of errors and misuse. One person initiates the process and the second completes it.",
"Buy Down Fees": "Buy Down Fees", "Buy Down Fees": "Buy Down Fees",
"Undo Write-off Description": "This action will reverse the write-off transaction and restore the loan to its previous active status. Please provide a note explaining the reason for this action.", "Undo Write-off Description": "This action will reverse the write-off transaction and restore the loan to its previous active status. Please provide a note explaining the reason for this action.",
"UploadDocumentHint": "Upload a PDF or image to generate a preview.", "UploadDocumentHint": "Upload a PDF or image to generate a preview."
"Create Savings Product": "Create Savings Product"
}, },
"auditTrail": { "auditTrail": {
"actions": { "actions": {
+3
View File
@@ -1107,6 +1107,7 @@
"Offices": "Oficinas", "Offices": "Oficinas",
"Options": "Opciones", "Options": "Opciones",
"Other Details": "Otros detalles", "Other Details": "Otros detalles",
"Overdue": "Vencido",
"Overdue Charges": "Comisiones Moratorias", "Overdue Charges": "Comisiones Moratorias",
"Password Preferences": "Preferencias de contraseña", "Password Preferences": "Preferencias de contraseña",
"Payment Type": "Tipo de pago", "Payment Type": "Tipo de pago",
@@ -2359,6 +2360,7 @@
"Show Closed Centers": "Mostrar Centros Cerrados", "Show Closed Centers": "Mostrar Centros Cerrados",
"Show Closed Groups": "Mostrar grupos cerrados", "Show Closed Groups": "Mostrar grupos cerrados",
"Show Payment Details": "Mostrar detalles de pago", "Show Payment Details": "Mostrar detalles de pago",
"Waive Penalties": "Renunciar a Penalizaciones",
"Slab": "Tramo", "Slab": "Tramo",
"Specific to Installment": "Específico para la cuota", "Specific to Installment": "Específico para la cuota",
"Staff": "Asesor", "Staff": "Asesor",
@@ -3170,6 +3172,7 @@
"No checker inbox data available for this search": "No hay datos de la bandeja de entrada del verificador disponibles para esta búsqueda.", "No checker inbox data available for this search": "No hay datos de la bandeja de entrada del verificador disponibles para esta búsqueda.",
"No client was found": "No se encontró ningún cliente", "No client was found": "No se encontró ningún cliente",
"No data found": "Datos no encontrados", "No data found": "Datos no encontrados",
"No penalties found": "No se encontraron penalizaciones",
"NoDocuments": "No hay documentos disponibles.", "NoDocuments": "No hay documentos disponibles.",
"NoFileSelected": "No se ha seleccionado ningún archivo", "NoFileSelected": "No se ha seleccionado ningún archivo",
"NoNotesAvailable": "No hay notas disponibles", "NoNotesAvailable": "No hay notas disponibles",
+6 -2
View File
@@ -1106,6 +1106,7 @@
"Offices": "Oficinas", "Offices": "Oficinas",
"Options": "Opciones", "Options": "Opciones",
"Other Details": "Otros detalles", "Other Details": "Otros detalles",
"Overdue": "Vencido",
"Overdue Charges": "Comisiones Moratorias", "Overdue Charges": "Comisiones Moratorias",
"Password Preferences": "Preferencias de contraseña", "Password Preferences": "Preferencias de contraseña",
"Payment Type": "Tipo de pago", "Payment Type": "Tipo de pago",
@@ -2359,6 +2360,7 @@
"Show Closed Centers": "Mostrar Centros Cerrados", "Show Closed Centers": "Mostrar Centros Cerrados",
"Show Closed Groups": "Mostrar grupos cerrados", "Show Closed Groups": "Mostrar grupos cerrados",
"Show Payment Details": "Mostrar detalles de pago", "Show Payment Details": "Mostrar detalles de pago",
"Waive Penalties": "Renunciar a Penalizaciones",
"Specific to Installment": "Específico para la cuota", "Specific to Installment": "Específico para la cuota",
"Slab": "Tramo", "Slab": "Tramo",
"Staff": "Asesor", "Staff": "Asesor",
@@ -2585,7 +2587,9 @@
"Buy down fee Expense": "Gastos por comisiones de compra inicial", "Buy down fee Expense": "Gastos por comisiones de compra inicial",
"Skip Interest Refund Transaction Posting": "Omitir el registro de la devolución de intereses", "Skip Interest Refund Transaction Posting": "Omitir el registro de la devolución de intereses",
"Incorporation Date": "Fecha de Constitución", "Incorporation Date": "Fecha de Constitución",
"Available Disbursement Amount (with Over Applied)": "Monto disponible para desembolso (con sobreaplicación)" "Available Disbursement Amount (with Over Applied)": "Monto disponible para desembolso (con sobreaplicación)",
"Overdue": "Vencido",
"Overdue Fees": "Cargos vencidos"
}, },
"links": { "links": {
"Community": "Comunidad", "Community": "Comunidad",
@@ -2910,7 +2914,6 @@
"Create Recurring Deposits Account": "Crear cuenta de depósitos recurrentes", "Create Recurring Deposits Account": "Crear cuenta de depósitos recurrentes",
"Create Report": "Crear reporte", "Create Report": "Crear reporte",
"Create SMS Campaign": "Crear campaña de SMS", "Create SMS Campaign": "Crear campaña de SMS",
"Create Savings Product": "Crear producto de ahorro",
"Create Savings Account": "Crear Cuenta de Ahorros", "Create Savings Account": "Crear Cuenta de Ahorros",
"Create Self Service User": "Crear usuario de autoservicio", "Create Self Service User": "Crear usuario de autoservicio",
"Create Share Product": "Crear producto de Acciones", "Create Share Product": "Crear producto de Acciones",
@@ -3171,6 +3174,7 @@
"No checker inbox data available for this search": "No hay datos de la bandeja de entrada del verificador disponibles para esta búsqueda.", "No checker inbox data available for this search": "No hay datos de la bandeja de entrada del verificador disponibles para esta búsqueda.",
"No client was found": "No se encontró ningún cliente", "No client was found": "No se encontró ningún cliente",
"No data found": "Datos no encontrados", "No data found": "Datos no encontrados",
"No penalties found": "No se encontraron penalizaciones",
"NoDocuments": "No hay documentos disponibles.", "NoDocuments": "No hay documentos disponibles.",
"NoFileSelected": "No se ha seleccionado ningún archivo", "NoFileSelected": "No se ha seleccionado ningún archivo",
"NoNotesAvailable": "No hay notas disponibles", "NoNotesAvailable": "No hay notas disponibles",
+3
View File
@@ -1107,6 +1107,7 @@
"Offices": "Des bureaux", "Offices": "Des bureaux",
"Options": "Possibilités", "Options": "Possibilités",
"Other Details": "Autres détails", "Other Details": "Autres détails",
"Overdue": "En retard",
"Overdue Charges": "Frais de retard", "Overdue Charges": "Frais de retard",
"Password Preferences": "Préférences de mot de passe", "Password Preferences": "Préférences de mot de passe",
"Payment Type": "Type de paiement", "Payment Type": "Type de paiement",
@@ -2358,6 +2359,7 @@
"Show Closed Centers": "Afficher les centres fermés", "Show Closed Centers": "Afficher les centres fermés",
"Show Closed Groups": "Afficher les groupes fermés", "Show Closed Groups": "Afficher les groupes fermés",
"Show Payment Details": "Afficher les détails du paiement", "Show Payment Details": "Afficher les détails du paiement",
"Waive Penalties": "Renoncer aux pénalités",
"Slab": "Tranche", "Slab": "Tranche",
"Specific to Installment": "Spécifique au versement", "Specific to Installment": "Spécifique au versement",
"Staff": "Personnel", "Staff": "Personnel",
@@ -3168,6 +3170,7 @@
"No checker inbox data available for this search": "Aucune donnée de boîte de réception du vérificateur disponible pour cette recherche.", "No checker inbox data available for this search": "Aucune donnée de boîte de réception du vérificateur disponible pour cette recherche.",
"No client was found": "Aucun client n'a été trouvé", "No client was found": "Aucun client n'a été trouvé",
"No data found": "Aucune donnée disponible", "No data found": "Aucune donnée disponible",
"No penalties found": "Aucune pénalité trouvée",
"NoDocuments": "Aucun document disponible.", "NoDocuments": "Aucun document disponible.",
"NoFileSelected": "Aucun fichier sélectionné", "NoFileSelected": "Aucun fichier sélectionné",
"NoNotesAvailable": "Aucune note disponible", "NoNotesAvailable": "Aucune note disponible",
+3
View File
@@ -1107,6 +1107,7 @@
"Offices": "Uffici", "Offices": "Uffici",
"Options": "Opzioni", "Options": "Opzioni",
"Other Details": "Altri dettagli", "Other Details": "Altri dettagli",
"Overdue": "Scaduto",
"Overdue Charges": "Addebiti scaduti", "Overdue Charges": "Addebiti scaduti",
"Password Preferences": "Preferenze password", "Password Preferences": "Preferenze password",
"Payment Type": "Modalità di pagamento", "Payment Type": "Modalità di pagamento",
@@ -2358,6 +2359,7 @@
"Show Closed Centers": "Mostra centri chiusi", "Show Closed Centers": "Mostra centri chiusi",
"Show Closed Groups": "Mostra gruppi chiusi", "Show Closed Groups": "Mostra gruppi chiusi",
"Show Payment Details": "Mostra i dettagli del pagamento", "Show Payment Details": "Mostra i dettagli del pagamento",
"Waive Penalties": "Rinuncia alle penalità",
"Slab": "Fascia", "Slab": "Fascia",
"Specific to Installment": "Specifico per la rata", "Specific to Installment": "Specifico per la rata",
"Staff": "Personale", "Staff": "Personale",
@@ -3169,6 +3171,7 @@
"No checker inbox data available for this search": "Nessun dato della casella di posta disponibile per questa ricerca.", "No checker inbox data available for this search": "Nessun dato della casella di posta disponibile per questa ricerca.",
"No client was found": "Nessun cliente trovato", "No client was found": "Nessun cliente trovato",
"No data found": "Nessun dato trovato", "No data found": "Nessun dato trovato",
"No penalties found": "Nessuna penalità trovata",
"NoDocuments": "Nessun documento disponibile.", "NoDocuments": "Nessun documento disponibile.",
"NoFileSelected": "Nessun file selezionato", "NoFileSelected": "Nessun file selezionato",
"NoNotesAvailable": "Nessuna nota disponibile", "NoNotesAvailable": "Nessuna nota disponibile",
+3
View File
@@ -1107,6 +1107,7 @@
"Offices": "부엌", "Offices": "부엌",
"Options": "옵션", "Options": "옵션",
"Other Details": "그 외 세부 사항", "Other Details": "그 외 세부 사항",
"Overdue": "연체",
"Overdue Charges": "연체료", "Overdue Charges": "연체료",
"Password Preferences": "비밀번호 기본 설정", "Password Preferences": "비밀번호 기본 설정",
"Payment Type": "결제 유형", "Payment Type": "결제 유형",
@@ -2359,6 +2360,7 @@
"Show Closed Centers": "닫힌 센터 표시", "Show Closed Centers": "닫힌 센터 표시",
"Show Closed Groups": "닫힌 그룹 표시", "Show Closed Groups": "닫힌 그룹 표시",
"Show Payment Details": "결제 세부정보 표시", "Show Payment Details": "결제 세부정보 표시",
"Waive Penalties": "패널티 면제",
"Slab": "구간", "Slab": "구간",
"Specific to Installment": "할부에만 해당", "Specific to Installment": "할부에만 해당",
"Staff": "직원", "Staff": "직원",
@@ -3169,6 +3171,7 @@
"No checker inbox data available for this search": "이 검색에 사용할 수 있는 검사기 받은 편지함 데이터가 없습니다.", "No checker inbox data available for this search": "이 검색에 사용할 수 있는 검사기 받은 편지함 데이터가 없습니다.",
"No client was found": "클라이언트를 찾을 수 없습니다.", "No client was found": "클라이언트를 찾을 수 없습니다.",
"No data found": "데이터가 없습니다", "No data found": "데이터가 없습니다",
"No penalties found": "패널티가 없습니다",
"NoDocuments": "사용 가능한 문서가 없습니다.", "NoDocuments": "사용 가능한 문서가 없습니다.",
"NoFileSelected": "선택된 파일 없음", "NoFileSelected": "선택된 파일 없음",
"NoNotesAvailable": "사용 가능한 메모 없음", "NoNotesAvailable": "사용 가능한 메모 없음",
+7 -3
View File
@@ -1107,6 +1107,7 @@
"Offices": "Biurai", "Offices": "Biurai",
"Options": "Galimybės", "Options": "Galimybės",
"Other Details": "Kitos detalės", "Other Details": "Kitos detalės",
"Overdue": "Pradelstas",
"Overdue Charges": "Pradelsti mokesčiai", "Overdue Charges": "Pradelsti mokesčiai",
"Password Preferences": "Slaptažodžio nuostatos", "Password Preferences": "Slaptažodžio nuostatos",
"Payment Type": "Mokėjimo tipas", "Payment Type": "Mokėjimo tipas",
@@ -2583,7 +2584,10 @@
"Income from Buy down fees": "Pajamos iš supirkimo mokesčių", "Income from Buy down fees": "Pajamos iš supirkimo mokesčių",
"Buy down fee Expense": "Supirkimo mokesčio išlaidos", "Buy down fee Expense": "Supirkimo mokesčio išlaidos",
"Skip Interest Refund Transaction Posting": "Praleisti palūkanų grąžinimo įrašą", "Skip Interest Refund Transaction Posting": "Praleisti palūkanų grąžinimo įrašą",
"Available Disbursement Amount (with Over Applied)": "Galimas išmokėjimo kiekis (su pertekliumi)" "Available Disbursement Amount (with Over Applied)": "Galimas išmokėjimo kiekis (su pertekliumi)",
"Waive Penalties": "Atsisakyti delspinigių",
"Overdue": "Pradelstas",
"Overdue Fees": "Delspinigiai"
}, },
"links": { "links": {
"Community": "bendruomenė", "Community": "bendruomenė",
@@ -2800,7 +2804,7 @@
"All Recurring Deposits": "Visi periodiniai indėliai", "All Recurring Deposits": "Visi periodiniai indėliai",
"All Savings": "Visos santaupos", "All Savings": "Visos santaupos",
"Savings Account Details": "Taupomosios sąskaitos informacija", "Savings Account Details": "Taupomosios sąskaitos informacija",
"Create Savings Product": "Sukurti taupymo produktą", "Create Savings Product": "Sukurkite taupymo produktą",
"Allocate Cash": "Paskirstykite grynuosius pinigus", "Allocate Cash": "Paskirstykite grynuosius pinigus",
"Allows you to create new fixed deposit product": "Ši parinktis leidžia sukurti naują terminuoto indėlio produktą.", "Allows you to create new fixed deposit product": "Ši parinktis leidžia sukurti naują terminuoto indėlio produktą.",
"This option allows you to create new users in your organization": "Ši parinktis leidžia sukurti naujų naudotojų jūsų organizacijoje", "This option allows you to create new users in your organization": "Ši parinktis leidžia sukurti naujų naudotojų jūsų organizacijoje",
@@ -2908,7 +2912,6 @@
"Create Recurring Deposits Account": "Sukurkite pasikartojančių indėlių sąskaitą", "Create Recurring Deposits Account": "Sukurkite pasikartojančių indėlių sąskaitą",
"Create Report": "Sukurti ataskaitą", "Create Report": "Sukurti ataskaitą",
"Create SMS Campaign": "Sukurkite SMS kampaniją", "Create SMS Campaign": "Sukurkite SMS kampaniją",
"Create Savings Product": "Sukurkite taupymo produktą",
"Create Savings Account": "Sukurti taupomąją sąskaitą", "Create Savings Account": "Sukurti taupomąją sąskaitą",
"Create Self Service User": "Sukurkite savitarnos vartotoją", "Create Self Service User": "Sukurkite savitarnos vartotoją",
"Create Share Product": "Sukurkite bendrinimo produktą", "Create Share Product": "Sukurkite bendrinimo produktą",
@@ -3169,6 +3172,7 @@
"No checker inbox data available for this search": "Nėra šios paieškos tikrinimo gautųjų duomenų.", "No checker inbox data available for this search": "Nėra šios paieškos tikrinimo gautųjų duomenų.",
"No client was found": "Klientas nerastas", "No client was found": "Klientas nerastas",
"No data found": "Duomenų nerasta", "No data found": "Duomenų nerasta",
"No penalties found": "Delspinigiai nerasti",
"NoDocuments": "Nėra dokumentų.", "NoDocuments": "Nėra dokumentų.",
"NoFileSelected": "Failas nepasirinktas", "NoFileSelected": "Failas nepasirinktas",
"NoNotesAvailable": "Nėra prieinamų pastabų", "NoNotesAvailable": "Nėra prieinamų pastabų",
+3
View File
@@ -1107,6 +1107,7 @@
"Offices": "Biroji", "Offices": "Biroji",
"Options": "Iespējas", "Options": "Iespējas",
"Other Details": "Citas detaļas", "Other Details": "Citas detaļas",
"Overdue": "Nokavējums",
"Overdue Charges": "Nokavētas maksas", "Overdue Charges": "Nokavētas maksas",
"Password Preferences": "Paroles preferences", "Password Preferences": "Paroles preferences",
"Payment Type": "Maksājuma veids", "Payment Type": "Maksājuma veids",
@@ -2358,6 +2359,7 @@
"Show Closed Centers": "Rādīt slēgtos centrus", "Show Closed Centers": "Rādīt slēgtos centrus",
"Show Closed Groups": "Rādīt slēgtās grupas", "Show Closed Groups": "Rādīt slēgtās grupas",
"Show Payment Details": "Rādīt maksājuma informāciju", "Show Payment Details": "Rādīt maksājuma informāciju",
"Waive Penalties": "Atteikties no sodiem",
"Slab": "Slānis", "Slab": "Slānis",
"Specific to Installment": "Īpaši uz nomaksu", "Specific to Installment": "Īpaši uz nomaksu",
"Staff": "Personāls", "Staff": "Personāls",
@@ -3169,6 +3171,7 @@
"No checker inbox data available for this search": "Šai meklēšanai nav pieejami pārbaudītāja iesūtnes dati.", "No checker inbox data available for this search": "Šai meklēšanai nav pieejami pārbaudītāja iesūtnes dati.",
"No client was found": "Klients netika atrasts", "No client was found": "Klients netika atrasts",
"No data found": "Dati nav atrasti", "No data found": "Dati nav atrasti",
"No penalties found": "Sodi nav atrasti",
"NoDocuments": "Nav pieejamu dokumentu.", "NoDocuments": "Nav pieejamu dokumentu.",
"NoFileSelected": "Nav izvēlēts fails", "NoFileSelected": "Nav izvēlēts fails",
"NoNotesAvailable": "Nav pieejamas piezīmes", "NoNotesAvailable": "Nav pieejamas piezīmes",
+3
View File
@@ -1105,6 +1105,7 @@
"Offices": "कार्यालयहरू", "Offices": "कार्यालयहरू",
"Options": "विकल्पहरू", "Options": "विकल्पहरू",
"Other Details": "अन्य विवरणहरू", "Other Details": "अन्य विवरणहरू",
"Overdue": "अतिदेय",
"Overdue Charges": "ओभरड्यू चार्जहरू", "Overdue Charges": "ओभरड्यू चार्जहरू",
"Password Preferences": "पासवर्ड प्राथमिकताहरू", "Password Preferences": "पासवर्ड प्राथमिकताहरू",
"Payment Type": "भुक्तानी प्रकार", "Payment Type": "भुक्तानी प्रकार",
@@ -2356,6 +2357,7 @@
"Show Closed Centers": "बन्द केन्द्रहरू देखाउनुहोस्", "Show Closed Centers": "बन्द केन्द्रहरू देखाउनुहोस्",
"Show Closed Groups": "बन्द समूहहरू देखाउनुहोस्", "Show Closed Groups": "बन्द समूहहरू देखाउनुहोस्",
"Show Payment Details": "भुक्तानी विवरणहरू देखाउनुहोस्", "Show Payment Details": "भुक्तानी विवरणहरू देखाउनुहोस्",
"Waive Penalties": "दण्ड छुट",
"Specific to Installment": "किस्ताको लागि विशिष्ट", "Specific to Installment": "किस्ताको लागि विशिष्ट",
"Slab": "स्तर", "Slab": "स्तर",
"Staff": "कर्मचारी", "Staff": "कर्मचारी",
@@ -3166,6 +3168,7 @@
"No checker inbox data available for this search": "यस खोजको लागि कुनै परीक्षक इनबक्स डाटा उपलब्ध छैन।", "No checker inbox data available for this search": "यस खोजको लागि कुनै परीक्षक इनबक्स डाटा उपलब्ध छैन।",
"No client was found": "कुनै ग्राहक भेटिएन", "No client was found": "कुनै ग्राहक भेटिएन",
"No data found": "कुनै डाटा फेला परेन", "No data found": "कुनै डाटा फेला परेन",
"No penalties found": "कुनै दण्ड फेला परेन",
"NoDocuments": "कुनै कागजात उपलब्ध छैन।", "NoDocuments": "कुनै कागजात उपलब्ध छैन।",
"NoFileSelected": "कुनै फाइल छानिएको छैन", "NoFileSelected": "कुनै फाइल छानिएको छैन",
"NoNotesAvailable": "कुनै नोटहरू उपलब्ध छैनन्", "NoNotesAvailable": "कुनै नोटहरू उपलब्ध छैनन्",
+3
View File
@@ -1107,6 +1107,7 @@
"Offices": "Escritórios", "Offices": "Escritórios",
"Options": "Opções", "Options": "Opções",
"Other Details": "Outros detalhes", "Other Details": "Outros detalhes",
"Overdue": "Vencido",
"Overdue Charges": "Cobranças vencidas", "Overdue Charges": "Cobranças vencidas",
"Password Preferences": "Preferências de senha", "Password Preferences": "Preferências de senha",
"Payment Type": "Tipo de pagamento", "Payment Type": "Tipo de pagamento",
@@ -2358,6 +2359,7 @@
"Show Closed Centers": "Mostrar Centros Fechados", "Show Closed Centers": "Mostrar Centros Fechados",
"Show Closed Groups": "Mostrar grupos fechados", "Show Closed Groups": "Mostrar grupos fechados",
"Show Payment Details": "Mostrar detalhes de pagamento", "Show Payment Details": "Mostrar detalhes de pagamento",
"Waive Penalties": "Isenção de penalidades",
"Slab": "Faixa", "Slab": "Faixa",
"Specific to Installment": "Específico para parcelamento", "Specific to Installment": "Específico para parcelamento",
"Staff": "Funcionários", "Staff": "Funcionários",
@@ -3169,6 +3171,7 @@
"No checker inbox data available for this search": "Não há dados da caixa de entrada do verificador disponíveis para esta pesquisa.", "No checker inbox data available for this search": "Não há dados da caixa de entrada do verificador disponíveis para esta pesquisa.",
"No client was found": "Nenhum cliente foi encontrado", "No client was found": "Nenhum cliente foi encontrado",
"No data found": "Nenhum dado encontrado", "No data found": "Nenhum dado encontrado",
"No penalties found": "Nenhuma penalidade encontrada",
"NoDocuments": "Nenhum documento disponível.", "NoDocuments": "Nenhum documento disponível.",
"NoFileSelected": "Nenhum arquivo selecionado", "NoFileSelected": "Nenhum arquivo selecionado",
"NoNotesAvailable": "Nenhuma nota disponível", "NoNotesAvailable": "Nenhuma nota disponível",
+3
View File
@@ -1106,6 +1106,7 @@
"Offices": "Ofisi", "Offices": "Ofisi",
"Options": "Chaguo", "Options": "Chaguo",
"Other Details": "Maelezo Mengine", "Other Details": "Maelezo Mengine",
"Overdue": "Zilizochelewa",
"Overdue Charges": "Gharama Zilizopitwa na Wakati", "Overdue Charges": "Gharama Zilizopitwa na Wakati",
"Password Preferences": "Mapendeleo ya Nenosiri", "Password Preferences": "Mapendeleo ya Nenosiri",
"Payment Type": "Aina ya malipo", "Payment Type": "Aina ya malipo",
@@ -2356,6 +2357,7 @@
"Show Closed Centers": "Onyesha Vituo Vilivyofungwa", "Show Closed Centers": "Onyesha Vituo Vilivyofungwa",
"Show Closed Groups": "Onyesha Vikundi Vilivyofungwa", "Show Closed Groups": "Onyesha Vikundi Vilivyofungwa",
"Show Payment Details": "Onyesha Maelezo ya Malipo", "Show Payment Details": "Onyesha Maelezo ya Malipo",
"Waive Penalties": "Acha Adhabu",
"Slab": "Kiwango", "Slab": "Kiwango",
"Specific to Installment": "Maalum kwa Installment", "Specific to Installment": "Maalum kwa Installment",
"Staff": "Wafanyakazi", "Staff": "Wafanyakazi",
@@ -3166,6 +3168,7 @@
"No checker inbox data available for this search": "Hakuna data ya kikasha kikagua inayopatikana kwa utafutaji huu.", "No checker inbox data available for this search": "Hakuna data ya kikasha kikagua inayopatikana kwa utafutaji huu.",
"No client was found": "Hakuna mteja aliyepatikana", "No client was found": "Hakuna mteja aliyepatikana",
"No data found": "Hakuna data iliyopatikana", "No data found": "Hakuna data iliyopatikana",
"No penalties found": "Hakuna adhabu zilizopatikana",
"NoDocuments": "Hakuna hati zinazopatikana.", "NoDocuments": "Hakuna hati zinazopatikana.",
"NoFileSelected": "Hakuna faili iliyochaguliwa", "NoFileSelected": "Hakuna faili iliyochaguliwa",
"NoNotesAvailable": "Hakuna noti zinazopatikana", "NoNotesAvailable": "Hakuna noti zinazopatikana",