Fixed linked user resolution

This commit is contained in:
2026-10-04 18:04:36 +02:00
parent 84a171d4a4
commit c9deba0427
4 changed files with 110 additions and 24 deletions
@@ -67,6 +67,7 @@ import {
Instrument,
Instruction,
Rule,
User,
OrganisationType,
ActivityStatus,
ApprovalStatus,
@@ -564,20 +565,70 @@ export const GovernancePage: React.FC = () => {
setIsOrgModalOpen(true);
};
const openEditOrg = (org: Organisation) => {
const openEditOrg = async (org: Organisation) => {
setEditingOrg(org);
const linkedUserId = org.userId || org.createdBy || usersDirectory[0]?.id || currentUserId;
const matchedUser = usersDirectory.find((u) => u.id === linkedUserId);
orgForm.reset({
name: org.name || '',
registrationNo: org.registrationNo || '',
email: org.email || '',
idNumber: matchedUser?.idNumber || (org as any).idNumber || '',
organisationUser: linkedUserId,
type: org.type || OrganisationType.Enterprise,
status: org.status ?? ActivityStatus.Active,
});
setIsOrgModalOpen(true);
// 1. Fetch fresh organisation details from API
let currentOrg = org;
try {
const fresh = await getOrganisation(org.id);
if (fresh) {
currentOrg = fresh;
}
} catch {
// fallback to passed org
}
// 2. Resolve linked user ID from organisation model
const targetUserId =
currentOrg.userId ||
(currentOrg as any).UserId ||
(currentOrg as any).organisationUser ||
(currentOrg as any).OrganisationUser ||
currentOrg.createdBy ||
'';
// 3. Find user in directory or fetch user record to get cached user profile and idNumber
let matchedUser = usersDirectory.find(
(u) => u.id && String(u.id).toLowerCase() === String(targetUserId).toLowerCase()
);
if (!matchedUser && targetUserId) {
try {
const fetchedUser = await getUser(targetUserId);
if (fetchedUser) {
matchedUser = fetchedUser;
}
} catch {
// user fetch failed
}
}
// 4. Ensure matched user is present in usersDirectory cache for dropdown rendering
if (matchedUser && !usersDirectory.some((u) => u.id === matchedUser!.id)) {
queryClient.setQueryData<User[]>(['users-directory'], (old = []) => [matchedUser!, ...old]);
}
// 5. Resolve idNumber from matched user profile or organisation
const resolvedIdNumber =
matchedUser?.idNumber ||
currentOrg.idNumber ||
(currentOrg as any).IdNumber ||
(org as any).idNumber ||
'';
const selectedUserId = matchedUser?.id || targetUserId || usersDirectory[0]?.id || currentUserId;
orgForm.reset({
name: currentOrg.name || '',
registrationNo: currentOrg.registrationNo || '',
email: currentOrg.email || '',
idNumber: resolvedIdNumber,
organisationUser: selectedUserId,
type: currentOrg.type || OrganisationType.Enterprise,
status: currentOrg.status ?? ActivityStatus.Active,
});
};
const openCreatePortfolio = () => {
@@ -1448,10 +1499,18 @@ export const GovernancePage: React.FC = () => {
</div>
<select
{...orgForm.register('organisationUser', {
onChange: (e) => {
const selected = usersDirectory.find((u) => u.id === e.target.value);
if (selected?.idNumber && !orgForm.getValues('idNumber')) {
orgForm.setValue('idNumber', selected.idNumber);
onChange: async (e) => {
const selectedId = e.target.value;
let selected = usersDirectory.find((u) => u.id === selectedId);
if (!selected && selectedId) {
try {
selected = await getUser(selectedId);
} catch {
// ignore
}
}
if (selected?.idNumber) {
orgForm.setValue('idNumber', selected.idNumber, { shouldValidate: true });
}
},
})}
@@ -27,6 +27,7 @@ import {
updateTenant,
getTenantOrganisations,
getOrganisations,
getUsers,
} from '../services/api';
import { Modal } from '../components/ui/Modal';
import { useToast } from '../components/ui/Toast';
@@ -35,8 +36,8 @@ import { ExportButton } from '../components/ui/ExportButton';
import { ExportColumn } from '../utils/exportUtils';
import { useTableSort, SortableTh } from '../utils/tableSort';
import { ActivityStatusBadge } from '../components/ui/StatusBadge';
import { Tenant, Organisation, TenantOrganisation, ActivityStatus, CreateTenantRequest, UpdateTenantRequest } from '../types';
import { formatDate, getOrganisationTypeLabel, getUserEmail } from '../lib/formatters';
import { Tenant, Organisation, TenantOrganisation, User, ActivityStatus, CreateTenantRequest, UpdateTenantRequest } from '../types';
import { formatDate, getOrganisationTypeLabel, getUserEmail, registerUserEmails } from '../lib/formatters';
import { useAuth } from '../auth/AuthProvider';
const createTenantSchema = z.object({
@@ -74,6 +75,19 @@ const TenantOrganisationsSubList: React.FC<{ tenantId: number | string; tenantNa
staleTime: 60000,
});
// Fetch user directory to resolve linked user email per organisation
const { data: usersDirectory = [] } = useQuery<User[]>({
queryKey: ['users-directory'],
queryFn: () => getUsers(1, 200),
staleTime: 5 * 60 * 1000,
});
React.useEffect(() => {
if (usersDirectory && usersDirectory.length > 0) {
registerUserEmails(usersDirectory);
}
}, [usersDirectory]);
const isLoading = loadingTenantOrgs || (loadingAllOrgs && tenantOrganisations.length > 0 && allOrganisations.length === 0);
return (
@@ -164,12 +178,23 @@ const TenantOrganisationsSubList: React.FC<{ tenantId: number | string; tenantNa
const regNo = org?.registrationNo || tOrg.organisation?.registrationNo || '—';
const orgType = org?.type ?? tOrg.organisation?.type;
const orgEmail = org?.email || tOrg.organisation?.email || '—';
const userEmailDisplay =
tOrg.creator?.email ||
getUserEmail(org?.userId) ||
getUserEmail(tOrg.createdBy) ||
getUserEmail(org?.createdBy) ||
const linkedUserId =
org?.userId ||
(org as any)?.UserId ||
(org as any)?.organisationUser ||
(org as any)?.OrganisationUser ||
(tOrg.organisation as any)?.userId ||
(tOrg as any)?.userId;
const matchedUser = linkedUserId
? usersDirectory.find((u) => u.id && String(u.id).toLowerCase() === String(linkedUserId).toLowerCase())
: null;
const userEmailDisplay =
matchedUser?.email ||
(linkedUserId ? getUserEmail(linkedUserId) : null) ||
(linkedUserId && String(linkedUserId).includes('@') ? String(linkedUserId) : null) ||
org?.email ||
'—';
const status = org?.status ?? tOrg.organisation?.status ?? tOrg.status;
const createdAt = org?.createdAt || tOrg.createdAt || tOrg.organisation?.createdAt;
@@ -181,7 +181,8 @@ export const normalizeOrganisation = (raw: any): Organisation => {
name: raw.name ?? raw.Name ?? null,
registrationNo: raw.registrationNo ?? raw.RegistrationNo ?? raw.regNo ?? raw.RegNo ?? null,
email: raw.email ?? raw.Email ?? null,
userId: raw.userId ?? raw.UserId ?? raw.organisationUser ?? raw.OrganisationUser ?? null,
userId: raw.userId ?? raw.UserId ?? raw.organisationUser ?? raw.OrganisationUser ?? raw.user_id ?? raw.linkedUser ?? raw.LinkedUser ?? null,
idNumber: raw.idNumber ?? raw.IdNumber ?? raw.id_number ?? null,
type: raw.type ?? raw.Type ?? raw.organisationType ?? raw.OrganisationType ?? 5,
status,
createdAt: raw.createdAt ?? raw.CreatedAt ?? new Date().toISOString(),
@@ -304,6 +304,7 @@ export interface Organisation {
registrationNo: string | null;
email: string | null;
userId?: string | null;
idNumber?: string | null;
type: OrganisationType;
status: ActivityStatus;
createdAt: string;