feat: non-individual entity UI improvements — hide individual-only sections, fix legal form display, send extended fields to API

- Hide Family Members tab/section for non-individual entities in client view, create flow, and preview
- Hide Financial & Classification section for non-individual entities in personal data and preview
- Fix legal form column in clients list — fetch legalForm from client API for correct display
- Add person/business icon before client name in list rows
- Remove unsupportedNpd filter — send authorizedSignatory, additionalInfo, dateOfRegistration to API
- Add registration number format hint (mat-hint) based on constitution type
- Add spacing between verification row and constitution type dropdowns
- Reduce table cell padding and prevent action buttons from being clipped
- Add missing MPL-2.0 license headers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-04 15:10:51 +05:30
co-authored by Claude Opus 4.6
parent ab01111424
commit b890d24bb7
522 changed files with 51991 additions and 21885 deletions
@@ -20,9 +20,6 @@ import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dial
import { DisableDialogComponent } from '../../../shared/disable-dialog/disable-dialog.component';
import { EnableDialogComponent } from '../../../shared/enable-dialog/enable-dialog.component';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { NgClass } from '@angular/common';
import { MatList, MatListItem } from '@angular/material/list';
import { MatDivider } from '@angular/material/divider';
import { MatCheckbox } from '@angular/material/checkbox';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -40,10 +37,6 @@ import { AuthService } from 'app/zitadel/auth.service';
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatList,
MatListItem,
NgClass,
MatDivider,
MatCheckbox
]
})
@@ -66,6 +59,65 @@ export class ViewRoleComponent implements OnInit {
groupings: string[] = [];
/** Stores the selected role */
selectedItem = '';
/** Grouped categories for sidebar */
roleGroups: { name: string; icon: string; categories: string[]; expanded: boolean }[] = [];
/** Mapping of category groupings to role groups */
private categoryToGroupMap: { [key: string]: string } = {
// Special & Authorisation
'special': 'Special & Authorisation',
'authorisation': 'Special & Authorisation',
// Configuration & Admin
'configuration': 'Configuration & Admin',
'organisation': 'Configuration & Admin',
'datatable': 'Configuration & Admin',
'jobs': 'Configuration & Admin',
// Portfolio — Client, Loan, Savings, FD, RD, Guarantor etc.
'portfolio': 'Portfolio',
'portfolio_group': 'Portfolio — Groups & Centers',
'portfolio_center': 'Portfolio — Groups & Centers',
// Transactions
'transaction_loan': 'Transactions — Loan',
'transaction_savings': 'Transactions — Savings',
'transaction_shareaccount': 'Transactions — Share Account',
// Accounting & Finance
'accounting': 'Accounting & Finance',
'loan_provisioning': 'Accounting & Finance',
'loan_reschedule': 'Accounting & Finance',
// Cash Management
'cash_mgmt': 'Cash Management',
// Reporting
'report': 'Reporting & Analytics',
// Share Account
'shareaccount': 'Share Account',
// Heading Menu (Top Navigation Bar)
'heading_menu': 'Heading Menu',
// Left Panel Menu (Sidebar)
'sidebar_menu': 'Left Panel Menu',
};
private groupIcons: { [key: string]: string } = {
'Special & Authorisation': 'fa-star',
'Configuration & Admin': 'fa-cog',
'Portfolio': 'fa-briefcase',
'Portfolio — Groups & Centers': 'fa-users',
'Transactions — Loan': 'fa-exchange-alt',
'Transactions — Savings': 'fa-piggy-bank',
'Transactions — Share Account': 'fa-share-alt',
'Accounting & Finance': 'fa-calculator',
'Cash Management': 'fa-cash-register',
'Reporting & Analytics': 'fa-chart-bar',
'Share Account': 'fa-chart-pie',
'Heading Menu': 'fa-th-list',
'Left Panel Menu': 'fa-bars',
};
/** Checks if its disabled */
isDisabled: Boolean = true;
/** Checks if there is any change in data */
@@ -88,6 +140,18 @@ export class ViewRoleComponent implements OnInit {
permissions: {
permissions: { code: string; id: number }[];
} = { permissions: [] };
/** Sub-grouped permissions for large categories */
permissionSubGroups: {
entity: string;
makerPerms: { code: string; id: number }[];
checkerPerms: { code: string; id: number }[];
expanded: boolean;
}[] = [];
/** Flat maker/checker split for small categories */
flatMakerPerms: { code: string; id: number }[] = [];
flatCheckerPerms: { code: string; id: number }[] = [];
/** Threshold for sub-grouping */
subGroupThreshold = 30;
/** Add role zitadel */
/**
@@ -114,6 +178,7 @@ export class ViewRoleComponent implements OnInit {
};
this.createForm();
this.groupRules();
this.buildRoleGroups();
this.selectedItem = 'special';
this.showPermissions('special');
this.route.params.subscribe((routeParams: any) => {
@@ -170,6 +235,285 @@ export class ViewRoleComponent implements OnInit {
}
}
/**
* Builds role groups from flat groupings
*/
buildRoleGroups() {
const groupMap: { [key: string]: string[] } = {};
for (const g of this.groupings) {
const groupName = this.categoryToGroupMap[g] || 'Other';
if (!groupMap[groupName]) {
groupMap[groupName] = [];
}
groupMap[groupName].push(g);
}
const order = [
'Special & Authorisation',
'Configuration & Admin',
'Portfolio',
'Portfolio — Groups & Centers',
'Transactions — Loan',
'Transactions — Savings',
'Transactions — Share Account',
'Accounting & Finance',
'Cash Management',
'Reporting & Analytics',
'Share Account',
'Heading Menu',
'Left Panel Menu',
'Other'
];
this.roleGroups = order
.filter(name => groupMap[name])
.map(name => ({
name,
icon: this.groupIcons[name] || 'fa-folder',
categories: groupMap[name],
expanded: name === 'Special & Authorisation'
}));
}
toggleGroup(group: any) {
group.expanded = !group.expanded;
}
selectAllGroup(group: any) {
const roster = this.formGroup.get('roster') as FormArray;
for (const cat of group.categories) {
const perms = this.tempPermissionUIData[cat]?.permissions || [];
for (const p of perms) {
roster.at(p.id).patchValue({ selected: true });
}
}
}
deselectAllGroup(group: any) {
const roster = this.formGroup.get('roster') as FormArray;
for (const cat of group.categories) {
const perms = this.tempPermissionUIData[cat]?.permissions || [];
for (const p of perms) {
roster.at(p.id).patchValue({ selected: false });
}
}
}
selectAllCategory(cat: string) {
const roster = this.formGroup.get('roster') as FormArray;
const perms = this.tempPermissionUIData[cat]?.permissions || [];
for (const p of perms) {
roster.at(p.id).patchValue({ selected: true });
}
}
deselectAllCategory(cat: string) {
const roster = this.formGroup.get('roster') as FormArray;
const perms = this.tempPermissionUIData[cat]?.permissions || [];
for (const p of perms) {
roster.at(p.id).patchValue({ selected: false });
}
}
getGroupPermCount(group: any): number {
let count = 0;
for (const cat of group.categories) {
count += this.tempPermissionUIData[cat]?.permissions?.length || 0;
}
return count;
}
getCatPermCount(cat: string): number {
return this.tempPermissionUIData[cat]?.permissions?.length || 0;
}
/** Entity display names */
private entityNames: { [key: string]: string } = {
'CLIENT': 'Client', 'CLIENTIMAGE': 'Client Image', 'CLIENTIDENTIFIER': 'Client Identifier',
'CLIENTNOTE': 'Client Note', 'CLIENTCHARGE': 'Client Charge',
'LOAN': 'Loan', 'LOANNOTE': 'Loan Note', 'LOANCHARGE': 'Loan Charge',
'LOANTRANSACTIONNOTE': 'Loan Transaction Note',
'SAVINGSACCOUNT': 'Savings Account', 'SAVINGSACCOUNTCHARGE': 'Savings Account Charge',
'SAVINGNOTE': 'Savings Note',
'FIXEDDEPOSITACCOUNT': 'Fixed Deposit Account',
'RECURRINGDEPOSITACCOUNT': 'Recurring Deposit Account',
'SHAREACCOUNT': 'Share Account',
'GROUP': 'Group', 'GROUPNOTE': 'Group Note',
'CENTER': 'Center',
'GUARANTOR': 'Guarantor', 'COLLATERAL': 'Collateral', 'DOCUMENT': 'Document',
'ACCOUNTTRANSFER': 'Account Transfer', 'STANDINGINSTRUCTION': 'Standing Instruction',
'RESCHEDULELOAN': 'Reschedule Loan',
'JOURNALENTRY': 'Journal Entry',
'TELLER': 'Teller', 'CASHIER': 'Cashier',
'ROLE': 'Role', 'USER': 'User', 'PERMISSION': 'Permission',
'CODE': 'Code', 'CODEVALUE': 'Code Value',
'OFFICE': 'Office', 'OFFICETRANSACTION': 'Office Transaction',
'STAFF': 'Staff', 'STAFFIMAGE': 'Staff Image',
'CHARGE': 'Charge', 'FUND': 'Fund',
'LOANPRODUCT': 'Loan Product', 'SAVINGSPRODUCT': 'Savings Product',
'FIXEDDEPOSITPRODUCT': 'Fixed Deposit Product', 'RECURRINGDEPOSITPRODUCT': 'Recurring Deposit Product',
'SHAREPRODUCT': 'Share Product',
'CURRENCY': 'Currency', 'DATATABLE': 'Data Table', 'CALENDAR': 'Calendar',
'CONFIGURATION': 'Configuration', 'AUDIT': 'Audit',
'REPORT': 'Report', 'HOOK': 'Hook', 'TEMPLATE': 'Template',
'PAYMENTTYPE': 'Payment Type', 'HOLIDAY': 'Holiday', 'WORKINGDAYS': 'Working Days',
'GLACCOUNT': 'GL Account', 'GLCLOSURE': 'GL Closure',
'FINANCIALACTIVITYACCOUNT': 'Financial Activity Account',
'TAXCOMPONENT': 'Tax Component', 'TAXGROUP': 'Tax Group',
'PRODUCTMIX': 'Product Mix', 'EXTERNALSERVICES': 'External Services',
'SCHEDULER': 'Scheduler', 'MEETING': 'Meeting',
'INTERESTRATECHART': 'Interest Rate Chart', 'FLOATINGRATE': 'Floating Rate',
'ACCOUNTNUMBERFORMAT': 'Account Number Format', 'ACCOUNTINGRULE': 'Accounting Rule',
'ENTITYMAPPING': 'Entity Mapping', 'PROVISION': 'Provisioning',
'REPORTMAILINGJOB': 'Report Mailing Job',
'PASSWORD_PREFERENCES': 'Password Preferences', 'ADDRESS': 'Address',
'CACHE': 'Cache',
};
/**
* Extracts entity name from permission code (e.g., CREATE_CLIENT -> CLIENT)
*/
private extractEntity(code: string): string {
// Common action prefixes to strip
const actions = [
'CREATE', 'READ', 'UPDATE', 'DELETE', 'APPROVE', 'REJECT', 'ACTIVATE', 'DEACTIVATE',
'CLOSE', 'DEPOSIT', 'WITHDRAWAL', 'WITHDRAW', 'DISBURSE', 'DISBURSETOSAVINGS',
'DISBURSALUNDO', 'DISBURSALLASTUNDO', 'REPAYMENT', 'ADJUST', 'WAIVE', 'WAIVEINTERESTPORTION',
'WRITEOFF', 'UNDOWRITEOFF', 'UNDOAPPROVAL', 'UNDOTRANSACTION', 'UNDO',
'CLOSEASRESCHEDULED', 'FORECLOSURE', 'REOPEN', 'REACTIVATE',
'POSTINTEREST', 'CALCULATEINTEREST', 'APPLYANNUALFEE', 'PREMATURECLOSE',
'UPDATEDEPOSITAMOUNT', 'WITHDRAWWITHHELD', 'UPDATELOANOFFICER', 'REMOVELOANOFFICER',
'BULKREASSIGN', 'REFUNDBYCASH', 'REFUNDBYTRANSFER',
'RECOVERGUARANTEES', 'RECOVERYPAYMENT',
'CREATESCHEDULEEXCEPTIONS', 'DELETESCHEDULEEXCEPTIONS',
'UPDATERUNNINGBALANCE', 'DEFINEOPENINGBALANCE', 'REVERSE',
'REGISTER', 'DEREGISTER', 'ENABLE', 'DISABLE', 'PERMISSIONS',
'INACTIVATE', 'PAY', 'PROPOSETRANSFER', 'ACCEPTTRANSFER', 'REJECTTRANSFER',
'WITHDRAWTRANSFER', 'ASSIGNSTAFF', 'UNASSIGNSTAFF', 'ASSIGNROLE', 'UPDATEROLE',
'DISASSOCIATECLIENTS', 'ASSOCIATECLIENTS', 'TRANSFERCLIENTS',
'ASSOCIATEGROUPS', 'DISASSOCIATEGROUPS',
'ALLOCATECASHIER', 'ALLOCATECASHTOCASHIER', 'SETTLECASHFROMCASHIER',
'EXECUTEJOB', 'EXECUTE', 'DIVIDEND',
'APPLYADDITIONALSHARES', 'APPROVEADDITIONALSHARES', 'REJECTADDITIONALSHARES', 'REDEEMSHARES',
];
// Try to match action_entity pattern
for (const action of actions) {
if (code.startsWith(action + '_')) {
const entity = code.substring(action.length + 1);
// Remove _CHECKER suffix
return entity.replace(/_CHECKER$/, '');
}
}
// Fallback: return the code itself
return code.replace(/_CHECKER$/, '');
}
private isChecker(code: string): boolean {
return code.endsWith('_CHECKER');
}
/**
* Splits permissions into maker/checker for flat display
*/
buildFlatMakerChecker() {
const perms = this.permissions.permissions;
this.flatMakerPerms = perms.filter(p => !this.isChecker(p.code));
this.flatCheckerPerms = perms.filter(p => this.isChecker(p.code));
}
/**
* Builds sub-groups for permissions in the current category
*/
buildSubGroups() {
const perms = this.permissions.permissions;
this.buildFlatMakerChecker();
if (perms.length <= this.subGroupThreshold) {
this.permissionSubGroups = [];
return;
}
const groups: { [entity: string]: { maker: { code: string; id: number }[]; checker: { code: string; id: number }[] } } = {};
for (const p of perms) {
const entity = this.extractEntity(p.code);
if (!groups[entity]) {
groups[entity] = { maker: [], checker: [] };
}
if (this.isChecker(p.code)) {
groups[entity].checker.push(p);
} else {
groups[entity].maker.push(p);
}
}
this.permissionSubGroups = Object.keys(groups).map(entity => ({
entity,
makerPerms: groups[entity].maker,
checkerPerms: groups[entity].checker,
expanded: false
}));
}
getEntityDisplayName(entity: string): string {
return this.entityNames[entity] || entity.replace(/_/g, ' ');
}
toggleSubGroup(subGroup: any) {
subGroup.expanded = !subGroup.expanded;
}
selectAllSubGroup(subGroup: any) {
const roster = this.formGroup.get('roster') as FormArray;
for (const p of [...subGroup.makerPerms, ...subGroup.checkerPerms]) {
roster.at(p.id).patchValue({ selected: true });
}
}
deselectAllSubGroup(subGroup: any) {
const roster = this.formGroup.get('roster') as FormArray;
for (const p of [...subGroup.makerPerms, ...subGroup.checkerPerms]) {
roster.at(p.id).patchValue({ selected: false });
}
}
getSubGroupCount(subGroup: any): number {
return subGroup.makerPerms.length + subGroup.checkerPerms.length;
}
selectAllMaker(perms: { code: string; id: number }[]) {
const roster = this.formGroup.get('roster') as FormArray;
for (const p of perms) {
roster.at(p.id).patchValue({ selected: true });
}
}
deselectAllMaker(perms: { code: string; id: number }[]) {
const roster = this.formGroup.get('roster') as FormArray;
for (const p of perms) {
roster.at(p.id).patchValue({ selected: false });
}
}
selectAllChecker(perms: { code: string; id: number }[]) {
const dialogRef = this.dialog.open(EnableDialogComponent, {
data: { enableContext: this.translateService.instant('labels.text.all Checker permissions for this section. This grants approval authority') }
});
dialogRef.afterClosed().subscribe((response: any) => {
if (response?.enable) {
const roster = this.formGroup.get('roster') as FormArray;
for (const p of perms) {
roster.at(p.id).patchValue({ selected: true });
}
}
});
}
deselectAllChecker(perms: { code: string; id: number }[]) {
const roster = this.formGroup.get('roster') as FormArray;
for (const p of perms) {
roster.at(p.id).patchValue({ selected: false });
}
}
/**
* Displays the permission for selected role
* @param grouping Selected Role
@@ -178,6 +522,7 @@ export class ViewRoleComponent implements OnInit {
this.permissions = this.tempPermissionUIData[grouping];
this.selectedItem = grouping;
this.previousGrouping = grouping;
this.buildSubGroups();
}
/**