Files
alla-allaos-fullstack/src/lib/auth/crm-access.ts
phaichayon b154a8de33 task-l
2026-06-22 10:22:45 +07:00

152 lines
4.6 KiB
TypeScript

import { and, eq, isNull } from 'drizzle-orm';
import { crmRoleProfiles, memberships } from '@/db/schema';
import { db } from '@/lib/db';
import {
ALL_PERMISSIONS,
type CrmApprovalAuthority,
type CrmOwnershipScope,
type CrmScopeMode,
getDefaultBusinessRole,
getRoleProfileDefinition,
isApprovalAuthority,
isMembershipRole,
isOwnershipScope,
isScopeMode,
resolveEffectivePermissions,
type MembershipRole,
type Permission,
type SystemRole
} from './rbac';
export interface ResolvedCrmAccess {
membershipRole: MembershipRole;
businessRole: string;
permissions: Permission[];
branchScopeIds: string[];
productTypeScopeIds: string[];
ownershipScope: CrmOwnershipScope;
branchScopeMode: CrmScopeMode;
productScopeMode: CrmScopeMode;
approvalAuthority: CrmApprovalAuthority;
roleProfileName: string | null;
}
type BranchScopedAccess = Pick<
ResolvedCrmAccess,
'branchScopeMode' | 'branchScopeIds'
> | {
branchScopeMode: string;
branchScopeIds: string[];
};
type ProductScopedAccess = Pick<
ResolvedCrmAccess,
'productScopeMode' | 'productTypeScopeIds'
> | {
productScopeMode: string;
productTypeScopeIds: string[];
};
function normalizeStringArray(value: unknown) {
return Array.isArray(value)
? [...new Set(value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0))]
: [];
}
export async function resolveCrmMembershipAccess(input: {
systemRole: SystemRole;
organizationId: string;
membership: typeof memberships.$inferSelect;
}): Promise<ResolvedCrmAccess> {
if (input.systemRole === 'super_admin') {
return {
membershipRole: 'admin',
businessRole: input.membership.businessRole,
permissions: ALL_PERMISSIONS,
branchScopeIds: [],
productTypeScopeIds: [],
ownershipScope: 'organization',
branchScopeMode: 'all',
productScopeMode: 'all',
approvalAuthority: 'final',
roleProfileName: 'System Admin'
};
}
const membershipRole = isMembershipRole(input.membership.role)
? input.membership.role
: 'user';
const businessRole = input.membership.businessRole || getDefaultBusinessRole(membershipRole);
const [roleProfile] = await db
.select()
.from(crmRoleProfiles)
.where(
and(
eq(crmRoleProfiles.organizationId, input.organizationId),
eq(crmRoleProfiles.code, businessRole),
eq(crmRoleProfiles.isActive, true),
isNull(crmRoleProfiles.deletedAt)
)
)
.limit(1);
const defaultDefinition = getRoleProfileDefinition(businessRole);
const ownershipScope: CrmOwnershipScope = isOwnershipScope(roleProfile?.ownershipScope ?? '')
? (roleProfile.ownershipScope as CrmOwnershipScope)
: (defaultDefinition?.ownershipScope ?? 'own');
const branchScopeMode: CrmScopeMode = isScopeMode(roleProfile?.branchScopeMode ?? '')
? (roleProfile.branchScopeMode as CrmScopeMode)
: (defaultDefinition?.branchScopeMode ?? 'assigned');
const productScopeMode: CrmScopeMode = isScopeMode(roleProfile?.productScopeMode ?? '')
? (roleProfile.productScopeMode as CrmScopeMode)
: (defaultDefinition?.productScopeMode ?? 'assigned');
const approvalAuthority: CrmApprovalAuthority = isApprovalAuthority(
roleProfile?.approvalAuthority ?? ''
)
? (roleProfile.approvalAuthority as CrmApprovalAuthority)
: (defaultDefinition?.approvalAuthority ?? 'none');
return {
membershipRole,
businessRole,
permissions: resolveEffectivePermissions({
systemRole: input.systemRole,
membershipRole,
businessRole,
rolePermissions: roleProfile?.permissions ?? defaultDefinition?.permissions ?? [],
directPermissions: input.membership.permissions
}),
branchScopeIds: normalizeStringArray(input.membership.branchScopeIds),
productTypeScopeIds: normalizeStringArray(input.membership.productTypeScopeIds),
ownershipScope,
branchScopeMode,
productScopeMode,
approvalAuthority,
roleProfileName: roleProfile?.name ?? defaultDefinition?.name ?? null
};
}
export function hasBranchScopeAccess(access: BranchScopedAccess, branchId?: string | null) {
if (!branchId) {
return true;
}
if (access.branchScopeMode === 'all' || access.branchScopeIds.length === 0) {
return true;
}
return access.branchScopeIds.includes(branchId);
}
export function hasProductScopeAccess(access: ProductScopedAccess, productTypeId?: string | null) {
if (!productTypeId) {
return true;
}
if (access.productScopeMode === 'all' || access.productTypeScopeIds.length === 0) {
return true;
}
return access.productTypeScopeIds.includes(productTypeId);
}