diff --git a/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.html b/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.html
index 1848744bd..913a8e7bb 100644
--- a/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.html
+++ b/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.html
@@ -1,7 +1,7 @@
{{ 'labels.heading.Capture Client Image' | translate }}
-
+
diff --git a/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.scss b/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.scss
index e69de29bb..613b35c1b 100644
--- a/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.scss
+++ b/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.scss
@@ -0,0 +1,10 @@
+@use '../../../../../assets/styles/colours' as colors;
+
+.capture-video {
+ width: 100%;
+ max-width: 640px;
+ height: auto;
+ object-fit: contain;
+ border-radius: 4px;
+ background-color: colors.$black;
+}
diff --git a/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.ts b/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.ts
index 6f3cbf994..480435fbb 100644
--- a/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.ts
+++ b/src/app/clients/clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component.ts
@@ -54,8 +54,13 @@ export class CaptureImageDialogComponent implements AfterViewInit, OnDestroy {
*/
startCamera() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+ const videoConstraints: MediaTrackConstraints = {
+ width: { ideal: 640 },
+ height: { ideal: 480 },
+ facingMode: 'user'
+ };
navigator.mediaDevices
- .getUserMedia({ video: true })
+ .getUserMedia({ video: videoConstraints })
.then((stream: MediaStream) => {
this.renderer.setProperty(this.video.nativeElement, 'srcObject', stream);
this.video.nativeElement.play();
@@ -95,13 +100,26 @@ export class CaptureImageDialogComponent implements AfterViewInit, OnDestroy {
/**
* Captures the image by drawing video state on canvas, then converts canvas state to data URL.
+ * Uses actual video dimensions to prevent aspect ratio distortion.
* See https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL for details.
*/
capture() {
this.isCaptured = true;
this.video.nativeElement.pause();
- this.canvas.nativeElement.getContext('2d').drawImage(this.video.nativeElement, 0, 0, 150, 150);
- this.clientImageDataURL = this.canvas.nativeElement.toDataURL();
+
+ // Get actual video stream dimensions to preserve aspect ratio
+ const videoWidth = this.video.nativeElement.videoWidth;
+ const videoHeight = this.video.nativeElement.videoHeight;
+
+ // Set canvas dimensions to match video stream
+ this.canvas.nativeElement.width = videoWidth;
+ this.canvas.nativeElement.height = videoHeight;
+
+ // Draw the video frame at full resolution without distortion
+ this.canvas.nativeElement.getContext('2d').drawImage(this.video.nativeElement, 0, 0, videoWidth, videoHeight);
+
+ // Convert to data URL with JPEG format for smaller file size
+ this.clientImageDataURL = this.canvas.nativeElement.toDataURL('image/jpeg', 0.9);
}
/**
diff --git a/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts b/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts
index 7c42a0b1b..cf5ffab06 100644
--- a/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts
+++ b/src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts
@@ -255,6 +255,12 @@ export class LoansAccountTermsStepComponent implements OnInit, OnChanges {
this.loansAccountTermsForm.addControl('enableDownPayment', new UntypedFormControl(enableDownPayment));
}
+ if (this.isFullTermTrancheEditable()) {
+ const allowFullTermForTranche =
+ this.loansAccountTermsData.allowFullTermForTranche ?? this.loanProduct?.allowFullTermForTranche ?? false;
+ this.loansAccountTermsForm.patchValue({ allowFullTermForTranche });
+ }
+
const allowAttributeOverrides = this.loansAccountTermsData.product.allowAttributeOverrides;
if (!allowAttributeOverrides.repaymentEvery) {
this.loansAccountTermsForm.controls.repaymentEvery.disable();
@@ -517,7 +523,8 @@ export class LoansAccountTermsStepComponent implements OnInit, OnChanges {
multiDisburseLoan: [false],
interestRateFrequencyType: [''],
balloonRepaymentAmount: [''],
- interestRecognitionOnDisbursementDate: [false]
+ interestRecognitionOnDisbursementDate: [false],
+ allowFullTermForTranche: [false]
});
}
@@ -699,4 +706,12 @@ export class LoansAccountTermsStepComponent implements OnInit, OnChanges {
collateral: this.collateralDataSource
};
}
+
+ /**
+ * Check if full term tranche option should be editable at loan level.
+ * Only available if the loan product has it enabled and is PROGRESSIVE schedule type.
+ */
+ isFullTermTrancheEditable(): boolean {
+ return this.isProgressive && this.loanProduct?.allowFullTermForTranche === true;
+ }
}
diff --git a/src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.html b/src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.html
index aec252581..3a05bed23 100644
--- a/src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.html
+++ b/src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.html
@@ -4,6 +4,11 @@
{{ loanDetails.maxOutstandingLoanBalance | formatNumber }}
+
+ {{ 'labels.inputs.Allow full term for each tranche' | translate }}:
+ {{ loanDetails.allowFullTermForTranche | yesNo }}
+
+
{{ 'labels.heading.Loan Tranche Details' | translate }}
diff --git a/src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.ts b/src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.ts
index cea55e50b..136ada550 100644
--- a/src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.ts
+++ b/src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.ts
@@ -27,6 +27,7 @@ import {
import { MatTooltip } from '@angular/material/tooltip';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { FormatNumberPipe } from '../../../pipes/format-number.pipe';
+import { YesnoPipe } from '../../../pipes/yesno.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -49,7 +50,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatRowDef,
MatRow,
DateFormatPipe,
- FormatNumberPipe
+ FormatNumberPipe,
+ YesnoPipe
]
})
export class LoanTrancheDetailsComponent implements OnInit {
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts
index 65378474b..c52374d9d 100644
--- a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.ts
@@ -146,6 +146,7 @@ export class LoanProductSettingsStepComponent implements OnInit {
multiDisburseLoan: this.loanProductsTemplate.multiDisburseLoan,
maxTrancheCount: this.loanProductsTemplate.maxTrancheCount,
outstandingLoanBalance: this.loanProductsTemplate.outstandingLoanBalance,
+ allowFullTermForTranche: this.loanProductsTemplate.allowFullTermForTranche,
enableDownPayment: this.loanProductsTemplate.enableDownPayment,
enableInstallmentLevelDelinquency: this.loanProductsTemplate.enableInstallmentLevelDelinquency,
loanScheduleType: this.loanProductsTemplate.loanScheduleType.code,
@@ -314,6 +315,7 @@ export class LoanProductSettingsStepComponent implements OnInit {
isInterestRecalculationEnabled: [false],
holdGuaranteeFunds: [false],
multiDisburseLoan: [false],
+ allowFullTermForTranche: [false],
allowAttributeConfiguration: [true],
allowPartialPeriodInterestCalculation: [false],
allowAttributeOverrides: this.formBuilder.group({
@@ -553,7 +555,10 @@ export class LoanProductSettingsStepComponent implements OnInit {
} else {
this.loanProductSettingsForm.removeControl('maxTrancheCount');
this.loanProductSettingsForm.removeControl('outstandingLoanBalance');
- this.loanProductSettingsForm.patchValue({ disallowExpectedDisbursements: false });
+ this.loanProductSettingsForm.patchValue({
+ disallowExpectedDisbursements: false,
+ allowFullTermForTranche: false
+ });
}
});
@@ -647,6 +652,7 @@ export class LoanProductSettingsStepComponent implements OnInit {
this.advancedTransactionProcessingStrategyDisabled = false;
this.isAdvancedTransactionProcessingStrategy = false;
this.loanProductSettingsForm.removeControl('chargeOffBehaviour');
+ this.loanProductSettingsForm.patchValue({ allowFullTermForTranche: false });
} else {
// Only Advanced Payment Allocation Strategy
this.transactionProcessingStrategyDataBase.some((cn: CodeName) => {
diff --git a/src/app/products/loan-products/models/loan-product.model.ts b/src/app/products/loan-products/models/loan-product.model.ts
index 9ad7c3c83..d66039b64 100644
--- a/src/app/products/loan-products/models/loan-product.model.ts
+++ b/src/app/products/loan-products/models/loan-product.model.ts
@@ -97,6 +97,7 @@ export interface LoanProduct {
creditAllocationAllocationTypes: OptionData[];
multiDisburseLoan: boolean;
maxTrancheCount: number;
+ allowFullTermForTranche: boolean;
disallowExpectedDisbursements: boolean;
allowApprovedDisbursedAmountsOverApplied: boolean;
overAppliedNumber: number;
diff --git a/src/assets/translations/en-US.json b/src/assets/translations/en-US.json
index 65dc2d853..33ee0a706 100644
--- a/src/assets/translations/en-US.json
+++ b/src/assets/translations/en-US.json
@@ -1599,6 +1599,7 @@
"Enable Dormancy Tracking": "Enable Dormancy Tracking",
"Enable Down Payment": "Enable Down Payment",
"Enable Multiple Disbursals": "Enable Multiple Disbursals",
+ "Allow full term for each tranche": "Allow full term for each tranche",
"Enabled": "Enabled",
"End Date": "End Date",
"End Point": "End Point",
@@ -3566,6 +3567,7 @@
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Leave this checkbox checked if the loan has Down Payment": "Leave this checkbox checked if the loan has Down Payment, A Down Payment is a sum a buyer pays upfront when purchasing a good. It represents a percentage of the total purchase price, and the balance is usually financed",
"Leave this checkbox unchecked": "Leave this checkbox unchecked if the loan is a single disburse loan. Check this checkbox if the loan is a multi disburse loan. See additional fields for additional information required for this type of loan.",
+ "Allow full term length for each tranche disbursement": "When enabled, each tranche disbursement will follow the full loan term schedule instead of fitting within the remaining term. Only available for PROGRESSIVE loan schedule type.",
"Loan Account": "Loan Account",
"Loan Product can be used to apply for Topup Loans": "If selected, the Loan Product can be used to apply for Top Up Loans.",
"Loan products may be assigned": "Loan products may be assigned to a fund set up by your financial institution. If available, the fund field can be used for tracking and reporting on groups of loans.",