fix(clients): stale signature bug, monthly turnover/annual income fields, senior citizen/PWD classification
- Fix signature-document reads picking the oldest upload instead of the latest across 7 call sites (CKYC tab, view/delete-signature dialogs, edit-wizard preview, cashier transaction entry, signatory service, record-master) by sorting on document id instead of an unsorted find(). - Add Monthly Turnover and Annual Income fields alongside the renamed Yearly Turnover field. - Add Senior Citizen (auto-detected from DOB) and Person with Disability classification, with a required disability-certificate upload gate before client creation can proceed. - Add Graduate and Diploma as distinct Education Levels, with diploma-specific degree options. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+88
-3
@@ -93,6 +93,54 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
/** Expose enum to template */
|
||||
readonly LegalFormId = LegalFormId;
|
||||
|
||||
/** Senior Citizen proof is optional; the PWD certificate is mandatory (see seniorPwdDocsComplete). Both, when
|
||||
* attached, are uploaded after client creation. */
|
||||
seniorCitizenProofFile: File | null = null;
|
||||
pwdCertificateFile: File | null = null;
|
||||
|
||||
/** Disability categories (per the Rights of Persons with Disabilities Act, 2016) — fetched from
|
||||
* m_code_value (code "PwdCategory") via the client template, same as occupationCategoryOptions.
|
||||
* Admins can add/reorder/deactivate entries from System -> Manage Codes without a code change. */
|
||||
pwdCategoryOptions: any[] = [];
|
||||
|
||||
/** Set true after a save attempt so the UI can highlight a missing required document */
|
||||
seniorPwdDocsTouched = false;
|
||||
|
||||
/** Senior Citizen proof is optional. The PWD certificate + disability type are mandatory
|
||||
* whenever the PWD checkbox is set. */
|
||||
get seniorPwdDocsComplete(): boolean {
|
||||
const pwdOk =
|
||||
!this.createClientForm.get('isPwd')?.value ||
|
||||
(!!this.pwdCertificateFile && !!this.createClientForm.get('pwdCategoryId')?.value);
|
||||
return pwdOk;
|
||||
}
|
||||
|
||||
markSeniorPwdDocsTouched(): void {
|
||||
this.seniorPwdDocsTouched = true;
|
||||
}
|
||||
|
||||
onSeniorCitizenProofSelected(event: any): void {
|
||||
const file = event.target?.files?.[0];
|
||||
if (file) {
|
||||
this.seniorCitizenProofFile = file;
|
||||
}
|
||||
}
|
||||
|
||||
removeSeniorCitizenProof(): void {
|
||||
this.seniorCitizenProofFile = null;
|
||||
}
|
||||
|
||||
onPwdCertificateSelected(event: any): void {
|
||||
const file = event.target?.files?.[0];
|
||||
if (file) {
|
||||
this.pwdCertificateFile = file;
|
||||
}
|
||||
}
|
||||
|
||||
removePwdCertificate(): void {
|
||||
this.pwdCertificateFile = null;
|
||||
}
|
||||
|
||||
/** Minimum date allowed. */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
/** Maximum date allowed. */
|
||||
@@ -157,6 +205,8 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
calculatedAgeDetail: string | null = null;
|
||||
/** Whether client is a minor (age < 10) */
|
||||
isMinor = false;
|
||||
/** Whether client is a senior citizen (age >= 60) — auto-detected from Date of Birth */
|
||||
isSeniorCitizen = false;
|
||||
/** Age breakdown for DB storage */
|
||||
ageBreakdown = { years: 0, months: 0, days: 0 };
|
||||
|
||||
@@ -525,7 +575,11 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
occupationCategoryId: data.occupationCategory?.id || data.occupationCategoryId || '',
|
||||
occupationId: data.occupation?.id || data.occupationId || '',
|
||||
monthlyIncome: data.monthlyIncome || '',
|
||||
annualIncome: data.annualIncome || '',
|
||||
annualTurnover: data.annualTurnover || '',
|
||||
monthlyTurnover: data.monthlyTurnover || '',
|
||||
isPwd: data.isPwd || false,
|
||||
pwdCategoryId: data.pwdCategory?.id || '',
|
||||
agricultureIncome: data.agricultureIncome || '',
|
||||
nonAgricultureIncome: data.nonAgricultureIncome || '',
|
||||
submittedOnDate: data.timeline?.submittedOnDate
|
||||
@@ -665,6 +719,13 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
annualIncome: [
|
||||
'',
|
||||
[
|
||||
Validators.pattern(/^\d+(\.\d{1,2})?$/),
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
annualTurnover: [
|
||||
'',
|
||||
[
|
||||
@@ -672,6 +733,15 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
monthlyTurnover: [
|
||||
'',
|
||||
[
|
||||
Validators.pattern(/^\d+(\.\d{1,2})?$/),
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
isPwd: [false],
|
||||
pwdCategoryId: [''],
|
||||
agricultureIncome: [
|
||||
'',
|
||||
[
|
||||
@@ -709,6 +779,7 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
this.occupationCategoryOptions = this.clientTemplate.occupationCategoryOptions || [];
|
||||
this.occupationOptions = this.clientTemplate.occupationOptions || [];
|
||||
this.salutationOptions = this.clientTemplate.salutationOptions || [];
|
||||
this.pwdCategoryOptions = this.clientTemplate.pwdCategoryOptions || [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -825,6 +896,7 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
this.ageBreakdown = { years, months, days };
|
||||
const wasMinor = this.isMinor;
|
||||
this.isMinor = years < 10;
|
||||
this.isSeniorCitizen = years >= 60;
|
||||
// Add/remove guardian form group based on minor status
|
||||
if (this.isMinor && !wasMinor) {
|
||||
this.createClientForm.addControl(
|
||||
@@ -850,12 +922,14 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
} else {
|
||||
this.calculatedAgeDetail = null;
|
||||
this.isMinor = false;
|
||||
this.isSeniorCitizen = false;
|
||||
this.ageBreakdown = { years: 0, months: 0, days: 0 };
|
||||
this.createClientForm.removeControl('guardianDetails');
|
||||
}
|
||||
} else {
|
||||
this.calculatedAgeDetail = null;
|
||||
this.isMinor = false;
|
||||
this.isSeniorCitizen = false;
|
||||
this.ageBreakdown = { years: 0, months: 0, days: 0 };
|
||||
this.createClientForm.removeControl('guardianDetails');
|
||||
}
|
||||
@@ -1548,6 +1622,9 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
if (n.includes('postgrad') || n === 'pg' || n.includes('master')) return 'postgraduate';
|
||||
if (n.includes('undergrad') || n === 'ug' || n.includes('bachelor') || n.includes('graduate'))
|
||||
return 'undergraduate';
|
||||
// Diploma is its own Education Level, distinct from School — checked before the school
|
||||
// keywords below so it doesn't get swept into that bucket.
|
||||
if (n.includes('diploma') || n.includes('polytechnic')) return 'diploma';
|
||||
if (
|
||||
n.includes('school') ||
|
||||
n.includes('10th') ||
|
||||
@@ -1556,7 +1633,6 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
n.includes('hsc') ||
|
||||
n.includes('matric') ||
|
||||
n.includes('intermediate') ||
|
||||
n.includes('diploma') ||
|
||||
n.includes('iti') ||
|
||||
n.includes('below')
|
||||
)
|
||||
@@ -1634,8 +1710,12 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
}
|
||||
|
||||
/**
|
||||
* Degrees filtered by the currently selected Education Level.
|
||||
* Returns all degrees if no level is selected, or category can't be determined.
|
||||
* Degrees filtered by the currently selected Education Level. Each DegreeQualification code
|
||||
* value is tagged with its owning level in `description` (e.g. "Undergraduate") when seeded —
|
||||
* match on that authoritative field first. Only fall back to the name-keyword heuristic
|
||||
* (getDegreeQualificationCategory) for older/untagged rows that predate this tagging, so
|
||||
* degrees never silently disappear because an admin added one without a description.
|
||||
* Returns all degrees if no level is selected.
|
||||
*/
|
||||
get filteredDegreeQualificationOptions(): any[] {
|
||||
const level = this.getSelectedEducationLevel();
|
||||
@@ -1643,6 +1723,9 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
const category = this.getEducationLevelCategory(level.name);
|
||||
if (!category) return this.degreeQualificationOptions;
|
||||
const filtered = this.degreeQualificationOptions.filter((d: any) => {
|
||||
if (d.description) {
|
||||
return this.getEducationLevelCategory(d.description) === category;
|
||||
}
|
||||
const c = this.getDegreeQualificationCategory(d.name);
|
||||
return c === category || c === '';
|
||||
});
|
||||
@@ -1805,6 +1888,8 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
// Store extended fields separately (not yet supported by Fineract API)
|
||||
// Add isMinor flag
|
||||
generalDetails.isMinor = this.isMinor;
|
||||
// isSeniorCitizen is auto-detected from DOB (not a form control) — add it explicitly
|
||||
generalDetails.isSeniorCitizen = this.isSeniorCitizen;
|
||||
|
||||
// Add guardian clientId for minors
|
||||
if (this.isMinor && generalDetails.guardianDetails) {
|
||||
|
||||
Reference in New Issue
Block a user