contitutional and relocation changes done based on document alignment
This commit is contained in:
parent
b357dbdcbb
commit
c23593bb11
@ -77,7 +77,6 @@ export default function App() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const currentRole = currentUser?.role || currentUser?.roleCode || '';
|
||||
const normalizedRole = String(currentRole).trim().toLowerCase();
|
||||
const hasRole = (roles: string[]) => {
|
||||
const normalizedTargetRoles = roles.map((r) => r.toLowerCase());
|
||||
const userRole = String(currentUser?.role || '').toLowerCase();
|
||||
|
||||
@ -47,7 +47,6 @@ export function Sidebar({ onLogout }: SidebarProps) {
|
||||
const hoverTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const currentRole = currentUser?.role || currentUser?.roleCode || '';
|
||||
const normalizedRole = String(currentRole).trim().toLowerCase();
|
||||
const hasRole = (roles: string[]) => {
|
||||
const normalizedTargetRoles = roles.map((r) => r.toLowerCase());
|
||||
const userRole = String(currentUser?.role || '').toLowerCase();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import { ConstitutionalChangePage } from "../pages/ConstitutionalChangePage"
|
||||
import { toast } from "sonner"
|
||||
|
||||
jest.mock("sonner", () => ({
|
||||
toast: {
|
||||
@ -138,4 +139,54 @@ describe("ConstitutionalChangePage", () => {
|
||||
|
||||
expect(screen.getByText(/^Dealer \*$/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("shows backend duplicate-open message on create conflict", async () => {
|
||||
const user = userEvent.setup()
|
||||
const { API } = await import("@/api/API")
|
||||
;(API.getDealers as jest.Mock).mockResolvedValueOnce({
|
||||
data: {
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
user: { id: "dealer-user-1" },
|
||||
constitutionType: "Proprietorship",
|
||||
businessName: "Dealer A",
|
||||
legalName: "Dealer A Pvt",
|
||||
dealerCode: { dealerCode: "DLR-1" },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
;(API.createConstitutionalChange as jest.Mock).mockRejectedValueOnce({
|
||||
response: {
|
||||
data: {
|
||||
message:
|
||||
"Open constitutional request CCR-1 already exists at ASM Review. Complete it before creating a new one.",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
setup()
|
||||
await user.click(screen.getByRole("button", { name: /new request/i }))
|
||||
await screen.findByRole("heading", {
|
||||
name: /create constitutional change request/i,
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole("combobox", { name: /dealer/i }))
|
||||
await user.click(await screen.findByText(/DLR-1 — Dealer A/i))
|
||||
|
||||
await user.click(screen.getByRole("combobox", { name: /proposed constitution/i }))
|
||||
await user.click(await screen.findByText(/^Partnership$/i))
|
||||
|
||||
const reasonField = screen.getByLabelText(/reason for constitutional change/i)
|
||||
await user.type(reasonField, "Need to onboard new partner")
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /submit request/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Open constitutional request CCR-1 already exists")
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -44,7 +44,7 @@ const formatStageRole = (role: string) =>
|
||||
// Document requirements mapping (same as in ConstitutionalChangePage) — SRS §12.2.4 by target constitution
|
||||
const documentRequirements: Record<string, number[]> = {
|
||||
'Partnership': [1, 2, 3, 4, 8, 9, 10, 16],
|
||||
'LLP': [1, 2, 3, 7, 8, 9, 10, 11, 16],
|
||||
'LLP': [1, 2, 3, 7, 8, 10, 11, 16],
|
||||
'Private Limited': [1, 2, 3, 5, 6, 7, 8, 10, 16],
|
||||
'Pvt Ltd': [1, 2, 3, 5, 6, 7, 8, 10, 16],
|
||||
'Proprietorship': [1, 2, 3, 10, 16]
|
||||
@ -151,6 +151,8 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
const [auditLogs, setAuditLogs] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isActionLoading, setIsActionLoading] = useState(false);
|
||||
/** Set when POST /action returns 4xx (apisauce does not throw — must check response.ok). */
|
||||
const [actionDialogError, setActionDialogError] = useState<string | null>(null);
|
||||
const [isUploadingDoc, setIsUploadingDoc] = useState(false);
|
||||
const [rejectDocDialogOpen, setRejectDocDialogOpen] = useState(false);
|
||||
const [rejectDocIndex, setRejectDocIndex] = useState<number | null>(null);
|
||||
@ -415,6 +417,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
|
||||
const handleAction = (type: 'approve' | 'reject' | 'sendBack' | 'revoke') => {
|
||||
setActionType(type);
|
||||
setActionDialogError(null);
|
||||
setIsActionDialogOpen(true);
|
||||
};
|
||||
|
||||
@ -436,6 +439,7 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
|
||||
try {
|
||||
setIsActionLoading(true);
|
||||
setActionDialogError(null);
|
||||
const actionPayload =
|
||||
actionType === 'approve'
|
||||
? OFFBOARDING_ACTIONS.APPROVE
|
||||
@ -448,7 +452,9 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
comments
|
||||
}) as any;
|
||||
|
||||
if (response.data.success) {
|
||||
const payload = response?.data as { success?: boolean; message?: string } | undefined;
|
||||
/** apisauce returns { ok: false } on 4xx without throwing — must branch on this. */
|
||||
if (response?.ok && payload?.success) {
|
||||
const actionText =
|
||||
actionType === 'approve' ? 'approved' :
|
||||
actionType === 'reject' ? 'rejected' :
|
||||
@ -457,12 +463,26 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
toast.success(`Request ${actionText} successfully`);
|
||||
setIsActionDialogOpen(false);
|
||||
setComments('');
|
||||
setActionDialogError(null);
|
||||
await fetchRequestDetails();
|
||||
return;
|
||||
}
|
||||
|
||||
const message =
|
||||
payload?.message ||
|
||||
(response as any)?.data?.error ||
|
||||
'Failed to submit action';
|
||||
setActionDialogError(message);
|
||||
const docGate = /mandatory documents/i.test(message);
|
||||
toast.error(message, { duration: docGate ? 14000 : 8000 });
|
||||
} catch (error) {
|
||||
console.error('Submit action error:', error);
|
||||
const message = (error as any)?.response?.data?.message || 'Failed to submit action';
|
||||
toast.error(message);
|
||||
const message =
|
||||
(error as any)?.response?.data?.message ||
|
||||
(error as any)?.message ||
|
||||
'Failed to submit action';
|
||||
setActionDialogError(message);
|
||||
toast.error(message, { duration: /mandatory documents/i.test(message) ? 14000 : 8000 });
|
||||
} finally {
|
||||
setIsActionLoading(false);
|
||||
}
|
||||
@ -1261,7 +1281,13 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
</div>
|
||||
|
||||
{/* Action Dialog */}
|
||||
<Dialog open={isActionDialogOpen} onOpenChange={setIsActionDialogOpen}>
|
||||
<Dialog
|
||||
open={isActionDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
setIsActionDialogOpen(open);
|
||||
if (!open) setActionDialogError(null);
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
@ -1278,6 +1304,24 @@ export function ConstitutionalChangeDetails({ requestId, onBack, currentUser }:
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmitAction} className="space-y-4">
|
||||
{actionDialogError && (
|
||||
<div
|
||||
role="alert"
|
||||
className="rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-900 flex gap-2"
|
||||
>
|
||||
<AlertCircle className="w-5 h-5 shrink-0 text-red-600" aria-hidden />
|
||||
<div className="min-w-0">
|
||||
<p className="font-medium">This action was not completed</p>
|
||||
<p className="mt-1 whitespace-pre-wrap break-words">{actionDialogError}</p>
|
||||
{/mandatory documents/i.test(actionDialogError) && (
|
||||
<p className="mt-2 text-red-800">
|
||||
Use the <strong>Documents</strong> tab to upload every required file for this constitution
|
||||
type, then approve again.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Label htmlFor="comments">
|
||||
{actionType === 'sendBack' || actionType === 'revoke' ? 'Remarks (required) *' : 'Comments *'}
|
||||
|
||||
@ -32,7 +32,7 @@ interface ConstitutionalChangePageProps {
|
||||
// Document requirements mapping (keys = DB `changeType` ENUM values)
|
||||
const documentRequirements: Record<string, number[]> = {
|
||||
'Partnership': [1, 2, 3, 4, 8, 9, 10, 16],
|
||||
'LLP': [1, 2, 3, 7, 8, 9, 10, 16],
|
||||
'LLP': [1, 2, 3, 7, 8, 10, 11, 16],
|
||||
'Private Limited': [1, 2, 3, 5, 6, 7, 8, 10, 16],
|
||||
'Proprietorship': [1, 2, 3, 10, 16]
|
||||
};
|
||||
@ -73,10 +73,8 @@ const getTypeColor = (type: string) => {
|
||||
case 'Proprietorship': return 'bg-purple-100 text-purple-700 border-purple-300';
|
||||
case 'Partnership': return 'bg-blue-100 text-blue-700 border-blue-300';
|
||||
case 'LLP':
|
||||
case 'LLP Conversion':
|
||||
return 'bg-indigo-100 text-indigo-700 border-indigo-300';
|
||||
case 'Private Limited':
|
||||
case 'Pvt Ltd':
|
||||
return 'bg-cyan-100 text-cyan-700 border-cyan-300';
|
||||
default: return 'bg-slate-100 text-slate-700 border-slate-300';
|
||||
}
|
||||
|
||||
@ -270,12 +270,13 @@ export function DealerConstitutionalChangePage({ onViewDetails }: DealerConstitu
|
||||
<ul className="text-blue-800 text-sm space-y-1">
|
||||
<li>• GST Registration Certificate</li>
|
||||
<li>• Firm PAN Copy</li>
|
||||
<li>• Partnership Deed (if applicable)</li>
|
||||
<li>• LLP Agreement (if applicable)</li>
|
||||
<li>• Certificate of Incorporation (if applicable)</li>
|
||||
<li>• MOA & AOA (if applicable)</li>
|
||||
<li>• Board Resolution</li>
|
||||
<li>• Aadhaar & PAN of all partners/directors</li>
|
||||
<li>• Self-attested KYC documents</li>
|
||||
<li>• Business Purchase Agreement (BPA)</li>
|
||||
<li>• Partnership Agreement / Firm Registration (if target is Partnership)</li>
|
||||
<li>• LLP Agreement / COI (if target is LLP)</li>
|
||||
<li>• MOA, AOA, COI (if target is Private Limited)</li>
|
||||
<li>• Cancelled Cheque</li>
|
||||
<li>• Declaration / Authorization Letter</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
Tabs, TabsContent, TabsList, TabsTrigger
|
||||
} from '@/components/ui/tabs';
|
||||
import { Globe, Shield, Mail, MapPin, SlidersHorizontal, Settings, FileText, Settings2 } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
// Services & Hooks
|
||||
|
||||
@ -50,7 +50,6 @@ interface ApplicationDetailsTabsProps {
|
||||
setShowDocumentsModal: (value: boolean) => void;
|
||||
setShowUploadForm: (value: boolean) => void;
|
||||
handleRetriggerEvaluators: () => void;
|
||||
handleCancelInterview: (interviewId: any) => void;
|
||||
handleRescheduleInterview: (interview: any) => void;
|
||||
setSelectedEvaluationForView: (value: any) => void;
|
||||
setShowFeedbackDetailsModal: (value: boolean) => void;
|
||||
@ -86,7 +85,6 @@ export function ApplicationDetailsTabs(props: ApplicationDetailsTabsProps) {
|
||||
setShowDocumentsModal,
|
||||
setShowUploadForm,
|
||||
handleRetriggerEvaluators,
|
||||
handleCancelInterview,
|
||||
handleRescheduleInterview,
|
||||
setSelectedEvaluationForView,
|
||||
setShowFeedbackDetailsModal,
|
||||
|
||||
@ -35,7 +35,6 @@ import {
|
||||
Mail,
|
||||
Grid3x3,
|
||||
List,
|
||||
AlertCircle,
|
||||
Loader2,
|
||||
Calendar,
|
||||
ArrowUpDown
|
||||
|
||||
@ -29,11 +29,9 @@ const workflowStages = [
|
||||
{ id: 3, name: 'DD ZM Review', key: 'DD_ZM_REVIEW', role: 'DD-ZM' },
|
||||
{ id: 4, name: 'ZBH Review', key: 'ZBH_REVIEW', role: 'ZBH' },
|
||||
{ id: 5, name: 'DD Lead Review', key: 'DD_LEAD_REVIEW', role: 'DD Lead' },
|
||||
{ id: 6, name: 'DD Head Approval', key: 'DD_HEAD_APPROVAL', role: 'DD Head' },
|
||||
{ id: 7, name: 'NBH Approval', key: 'NBH_APPROVAL', role: 'NBH' },
|
||||
{ id: 8, name: 'Legal Clearance', key: 'LEGAL_CLEARANCE', role: 'Legal Admin' },
|
||||
{ id: 9, name: 'NBH Clearance with EOR', key: 'NBH_CLEARANCE_EOR', role: 'NBH' },
|
||||
{ id: 10, name: 'Relocation Complete', key: 'COMPLETED', role: 'System' }
|
||||
{ id: 6, name: 'NBH Approval', key: 'NBH_APPROVAL', role: 'NBH' },
|
||||
{ id: 7, name: 'Legal Clearance', key: 'LEGAL_CLEARANCE', role: 'Legal Admin' },
|
||||
{ id: 8, name: 'Relocation Complete', key: 'COMPLETED', role: 'System' }
|
||||
];
|
||||
|
||||
/** Map API stage / status label to 1-based workflow row index; 0 = unknown; length+1 = finished */
|
||||
@ -296,15 +294,6 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
if (response.data.success) {
|
||||
const req = response.data.request;
|
||||
setRequest(req);
|
||||
|
||||
const currentStage = req.currentStage;
|
||||
if (
|
||||
currentStage === 'NBH_CLEARANCE_EOR' ||
|
||||
currentStage === 'NBH Clearance with EOR' ||
|
||||
req.status === 'Completed'
|
||||
) {
|
||||
fetchEorChecklist(req.id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fetch relocation request details error:', error);
|
||||
@ -351,7 +340,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
const timelineResolvedOrdinal = relocationTimelineResolvedOrdinal(timelineEntries);
|
||||
const auditResolvedOrdinal = relocationAuditResolvedOrdinal(auditLogs);
|
||||
const dbOrdinal = request ? getDbStageOrdinal() : 1;
|
||||
/** Audit/timeline can reference later steps (e.g. NBH EOR) while the request still sits at NBH Approval — do not use that to drive the active step. */
|
||||
/** Audit/timeline can reference later steps while the request still sits in a prior stage — do not use that to drive the active step. */
|
||||
const workflowProgressMismatch =
|
||||
Boolean(request) &&
|
||||
Math.max(timelineResolvedOrdinal, auditResolvedOrdinal) > dbOrdinal &&
|
||||
@ -365,7 +354,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
request?.status === 'Completed' ||
|
||||
request?.currentStage === 'Completed' ||
|
||||
dbOrdinal >= workflowStages.length + 1;
|
||||
/** Match backend: N/10 while on pipeline (NBH EOR = 9 → 90%); 100% only when completed — avoids stale API 100% at NBH EOR. */
|
||||
/** Match backend: N/(pipeline+1) while in flight; 100% only when completed. */
|
||||
const timelineProgressPct = allWorkflowComplete
|
||||
? 100
|
||||
: Math.min(100, Math.round((dbOrdinal / workflowStages.length) * 100));
|
||||
@ -422,7 +411,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
request.currentStage &&
|
||||
request.currentStage !== 'ASM Review' &&
|
||||
request.currentStage !== 'Rejected';
|
||||
const canRevoke = showActions && ['ZBH', 'DD Lead', 'DD Head', 'NBH', 'Legal Admin', 'Super Admin'].includes(currentUser?.role || '');
|
||||
const canRevoke = showActions && ['ZBH', 'DD Lead', 'NBH', 'Legal Admin', 'Super Admin'].includes(currentUser?.role || '');
|
||||
const requiresDocGate = request?.currentStage === 'NBH Approval' || request?.currentStage === 'Legal Clearance';
|
||||
const canApprove = showActions && (!requiresDocGate || (missingRequiredDocs.length === 0 && pendingVerificationDocs.length === 0));
|
||||
|
||||
@ -696,9 +685,6 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
<TabsList className="w-max min-w-full justify-start">
|
||||
<TabsTrigger value="workflow">Workflow Progress</TabsTrigger>
|
||||
<TabsTrigger value="documents">Documents</TabsTrigger>
|
||||
{(request.currentStage === 'NBH Clearance with EOR' || request.status === 'Completed' || request.currentStage === 'NBH_CLEARANCE_EOR') && (
|
||||
<TabsTrigger value="eor">EOR Checklist</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="history">History & Audit Trail</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
@ -1039,7 +1025,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
{doc.status === 'Pending Verification' && (() => {
|
||||
const role = currentUser?.role || currentUser?.roleCode || '';
|
||||
// SRS — only authorized review roles can verify relocation documents
|
||||
return ['DD Lead', 'DD Head', 'NBH', 'Legal Admin', 'DD Admin', 'Super Admin', 'SUPER_ADMIN', 'DD_ADMIN'].includes(role);
|
||||
return ['DD Lead', 'NBH', 'Legal Admin', 'DD Admin', 'Super Admin', 'SUPER_ADMIN', 'DD_ADMIN'].includes(role);
|
||||
})() && (
|
||||
<>
|
||||
<Button
|
||||
@ -1134,7 +1120,7 @@ export function RelocationRequestDetails({ requestId, onBack, currentUser }: Rel
|
||||
{(!eorChecklist.items || eorChecklist.items.length === 0) ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center text-slate-500 py-8 text-sm">
|
||||
No checklist rows returned. Use "Try Refreshing" above or reload the page; rows are created when the request enters NBH Clearance with EOR.
|
||||
No checklist rows returned. Use "Try Refreshing" above or reload the page.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
|
||||
@ -213,14 +213,6 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
const isDDLeadStage = currentStage === 'DD Lead' || currentStage === 'DD Lead Review';
|
||||
const isDDLead = userRoleCode === 'DD_LEAD' || userRoleCode === 'DD LEAD';
|
||||
|
||||
const canApprove = isCurrentlyAssigned &&
|
||||
!isFinalState &&
|
||||
!isSettlementPhase &&
|
||||
!hasAlreadyPartiallyApproved &&
|
||||
!(currentStage === 'Legal' && legalStageApproved) &&
|
||||
!(isDDLead && isDDLeadStage && !hasUploadedPPT) &&
|
||||
!(currentStage === 'DD Admin' && !isLwdReached);
|
||||
|
||||
const isLwdReached = (() => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
@ -231,6 +223,14 @@ export function ResignationDetails({ resignationId, onBack, currentUser }: Resig
|
||||
return today >= lwd;
|
||||
})();
|
||||
|
||||
const canApprove = isCurrentlyAssigned &&
|
||||
!isFinalState &&
|
||||
!isSettlementPhase &&
|
||||
!hasAlreadyPartiallyApproved &&
|
||||
!(currentStage === 'Legal' && legalStageApproved) &&
|
||||
!(isDDLead && isDDLeadStage && !hasUploadedPPT) &&
|
||||
!(currentStage === 'DD Admin' && !isLwdReached);
|
||||
|
||||
return {
|
||||
canApprove,
|
||||
canSendBack: isCurrentlyAssigned && !isFinalState && !isSettlementPhase && stageIndex > 0,
|
||||
|
||||
@ -4,24 +4,14 @@
|
||||
|
||||
const PRIVATE_LIMITED = 'Private Limited';
|
||||
const LLP = 'LLP';
|
||||
const LLP_CONVERSION = 'LLP Conversion';
|
||||
const PARTNERSHIP = 'Partnership';
|
||||
const PARTNERSHIP_CHANGE = 'Partnership Change';
|
||||
const PROPRIETORSHIP = 'Proprietorship';
|
||||
const DIRECTOR_CHANGE = 'Director Change';
|
||||
const OWNERSHIP_TRANSFER = 'Ownership Transfer';
|
||||
const COMPANY_FORMATION = 'Company Formation';
|
||||
|
||||
const ALL: string[] = [
|
||||
PROPRIETORSHIP,
|
||||
PARTNERSHIP,
|
||||
LLP_CONVERSION,
|
||||
LLP,
|
||||
PRIVATE_LIMITED,
|
||||
COMPANY_FORMATION,
|
||||
OWNERSHIP_TRANSFER,
|
||||
PARTNERSHIP_CHANGE,
|
||||
DIRECTOR_CHANGE
|
||||
PRIVATE_LIMITED
|
||||
];
|
||||
|
||||
export function isRegisteredConstitutionalChangeType(value: string): boolean {
|
||||
@ -44,14 +34,9 @@ export function normalizeToConstitutionalChangeType(raw: string | null | undefin
|
||||
) {
|
||||
return PRIVATE_LIMITED;
|
||||
}
|
||||
if (compact.includes('llp') && compact.includes('conversion')) return LLP_CONVERSION;
|
||||
if (compact.includes('llp')) return LLP;
|
||||
if (compact.includes('partnership') && compact.includes('change')) return PARTNERSHIP_CHANGE;
|
||||
if (compact.includes('partnership')) return PARTNERSHIP;
|
||||
if (compact.includes('proprietorship') || compact === 'sole proprietorship') return PROPRIETORSHIP;
|
||||
if (compact.includes('director')) return DIRECTOR_CHANGE;
|
||||
if (compact.includes('ownership') && compact.includes('transfer')) return OWNERSHIP_TRANSFER;
|
||||
if (compact.includes('company') && compact.includes('formation')) return COMPANY_FORMATION;
|
||||
const exact = ALL.find((v) => v.toLowerCase() === s.toLowerCase());
|
||||
return exact || null;
|
||||
}
|
||||
|
||||
@ -2,20 +2,27 @@
|
||||
|
||||
export type UserRole =
|
||||
| 'DD-ZM'
|
||||
| 'DD_ZM'
|
||||
| 'RBM'
|
||||
| 'DD'
|
||||
| 'ZBH'
|
||||
| 'DD Lead'
|
||||
| 'DD_LEAD'
|
||||
| 'DD Head'
|
||||
| 'DD_HEAD'
|
||||
| 'NBH'
|
||||
| 'DD Admin'
|
||||
| 'DD_ADMIN'
|
||||
| 'Legal Admin'
|
||||
| 'LEGAL_ADMIN'
|
||||
| 'Super Admin'
|
||||
| 'SUPER_ADMIN'
|
||||
| 'DD AM'
|
||||
| 'FDD'
|
||||
| 'DDL'
|
||||
| 'Finance'
|
||||
| 'Finance Admin'
|
||||
| 'FINANCE_ADMIN'
|
||||
| 'Dealer'
|
||||
| 'ASM'
|
||||
| 'CCO'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user