Files
prop-ai-hr/mobile-uni/src/services/position.ts
T

111 lines
3.5 KiB
TypeScript

import type { MobileIdentity, RoleKey } from '@/types/api';
import { apiRequest, isAuthApiError } from './api';
import {
clearOrgPosition,
getSelectedPosition,
hasOrgPosition,
isSupervisorLearnerMode,
leaveSupervisorLearnerMode,
normalizePosition,
roleKey,
saveOrgPosition,
type UserPosition
} from './auth';
export const fetchMobileIdentity = () => apiRequest<MobileIdentity>({
url: '/api/aihr/mobile/me',
method: 'GET',
timeout: 15000,
clearAuthOnUnauthorized: false
});
export const userTabPages = new Set([
'/pages/user/today/index',
'/pages/user/practice/index',
'/pages/user/sop/index',
'/pages/user/profile/index'
]);
export const isUserRoute = (url: string) => url.startsWith('/pages/user/');
export const isSupervisorRoute = (url: string) => url.startsWith('/pages/supervisor/');
export const routeForMobileRole = (role: RoleKey, requested: string) => {
if (role === 'supervisor' && isUserRoute(requested) && !isSupervisorLearnerMode()) {
return '/pages/supervisor/index/index';
}
if (role === 'user' && isSupervisorRoute(requested)) {
return '/pages/user/today/index';
}
return requested;
};
export const hydrateMobileRole = async (): Promise<RoleKey> => {
try {
const identity = await fetchMobileIdentity();
const role = identity.role === 'supervisor' ? 'supervisor' : 'user';
uni.setStorageSync(roleKey, role);
if (role !== 'supervisor') leaveSupervisorLearnerMode();
return role;
} catch {
// ponytail: role lookup failure fails closed; never reuse a stale supervisor cache.
uni.removeStorageSync(roleKey);
return 'user';
}
};
export const positionSetupUrl = (redirect: string) =>
`/pages/auth/position/index?redirect=${encodeURIComponent(redirect || '/pages/user/today/index')}`;
export const goToMobileTarget = (url: string) => {
if (userTabPages.has(url)) {
uni.switchTab({ url });
return;
}
uni.redirectTo({ url });
};
export type OrgPositionResult =
| { status: 'matched'; position: UserPosition }
| { status: 'unmatched' }
| { status: 'unauthorized' }
| { status: 'error' };
export const fetchOrgPositionResult = async (): Promise<OrgPositionResult> => {
try {
const identity = await fetchMobileIdentity();
const officialPosition = saveOrgPosition(identity.positionName);
if (!officialPosition) {
clearOrgPosition();
return { status: 'unmatched' };
}
return { status: 'matched', position: officialPosition };
} catch (error) {
return { status: isAuthApiError(error) ? 'unauthorized' : 'error' };
}
};
export const hydratePositionFromOrg = async (): Promise<UserPosition | ''> => {
const result = await fetchOrgPositionResult();
if (result.status === 'matched') return result.position;
if (result.status === 'unmatched') return '';
return hasOrgPosition() ? getSelectedPosition() : '';
};
export const nextUserRouteAfterLogin = async (redirect: string) => {
const role = await hydrateMobileRole();
const roleRoute = routeForMobileRole(role, redirect);
if (roleRoute !== redirect) return roleRoute;
if (!isUserRoute(redirect)) return redirect;
const officialPosition = await hydratePositionFromOrg();
if (normalizePosition(officialPosition) || hasOrgPosition()) return redirect;
return positionSetupUrl(redirect);
};
export const ensureEmployeePosition = async (redirect: string) => {
const nextRoute = await nextUserRouteAfterLogin(redirect);
if (nextRoute === redirect) return true;
uni.navigateTo({ url: nextRoute });
return false;
};