diff --git a/src/api/API.ts b/src/api/API.ts index 217507d..075b253 100644 --- a/src/api/API.ts +++ b/src/api/API.ts @@ -20,7 +20,7 @@ export const API = { createRegion: (data: any) => client.post('/master/regions', data), updateRegion: (id: string, data: any) => client.put(`/master/regions/${id}`, data), getRegions: () => client.get('/master/regions'), - getOutlets: () => client.get('/master/outlets'), + getOutlets: () => client.get('/outlets'), getOutletByCode: (code: string) => client.get(`/master/outlets/code/${code}`), getStates: (params?: any) => client.get('/master/states', typeof params === 'string' ? { zoneId: params } : params), getDistricts: (params?: any) => client.get('/master/districts', typeof params === 'string' ? { stateId: params } : params), diff --git a/src/features/fnf/pages/FinanceFnFDetailsPage.tsx b/src/features/fnf/pages/FinanceFnFDetailsPage.tsx index f29b2fa..ceb7d3f 100644 --- a/src/features/fnf/pages/FinanceFnFDetailsPage.tsx +++ b/src/features/fnf/pages/FinanceFnFDetailsPage.tsx @@ -480,8 +480,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr toast.success('Payable item added'); fetchFnFDetails(); } - } catch (error) { - toast.error('Failed to add payable item'); + } catch (error: any) { + toast.error(error.response?.data?.message || 'Failed to add payable item'); } }; @@ -521,8 +521,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr }); toast.success('Changes saved'); fetchFnFDetails(false); - } catch (error) { - toast.error('Failed to update item'); + } catch (error: any) { + toast.error(error.response?.data?.message || 'Failed to update item'); fetchFnFDetails(false); } }; @@ -536,8 +536,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr toast.info('Payable item removed'); fetchFnFDetails(); } - } catch (error) { - toast.error('Failed to delete item'); + } catch (error: any) { + toast.error(error.response?.data?.message || 'Failed to delete item'); } }; @@ -566,8 +566,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr toast.success('Receivable item added'); fetchFnFDetails(); } - } catch (error) { - toast.error('Failed to add receivable item'); + } catch (error: any) { + toast.error(error.response?.data?.message || 'Failed to add receivable item'); } }; @@ -607,8 +607,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr }); toast.success('Changes saved'); fetchFnFDetails(false); - } catch (error) { - toast.error('Failed to update item'); + } catch (error: any) { + toast.error(error.response?.data?.message || 'Failed to update item'); fetchFnFDetails(false); } }; @@ -619,8 +619,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr setReceivableItems(receivableItems.filter(item => item.id !== id)); toast.info('Receivable item removed'); fetchFnFDetails(); - } catch (error) { - toast.error('Failed to delete item'); + } catch (error: any) { + toast.error(error.response?.data?.message || 'Failed to delete item'); } }; @@ -649,8 +649,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr toast.success('Deduction item added'); fetchFnFDetails(); } - } catch (error) { - toast.error('Failed to add deduction item'); + } catch (error: any) { + toast.error(error.response?.data?.message || 'Failed to add deduction item'); } }; @@ -690,8 +690,8 @@ export function FinanceFnFDetailsPage({ fnfId, onBack }: FinanceFnFDetailsPagePr }); toast.success('Changes saved'); fetchFnFDetails(false); - } catch (error) { - toast.error('Failed to update item'); + } catch (error: any) { + toast.error(error.response?.data?.message || 'Failed to update item'); fetchFnFDetails(false); } }; diff --git a/src/features/fnf/pages/FnFDetails.tsx b/src/features/fnf/pages/FnFDetails.tsx index b33d5c7..8278703 100644 --- a/src/features/fnf/pages/FnFDetails.tsx +++ b/src/features/fnf/pages/FnFDetails.tsx @@ -21,7 +21,7 @@ import { } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; -import { Plus, Pencil, Trash2, Building2, CreditCard, Landmark } from "lucide-react"; +import { Plus, Pencil, Trash2, Building2, Landmark } from "lucide-react"; import { Dialog, DialogContent, @@ -42,7 +42,7 @@ import { import { Progress } from "@/components/ui/progress"; import { useState, useEffect } from "react"; import { toast } from "sonner"; -import { settlementService } from "@/services/settlement.service"; + import { API } from "@/api/API"; import { useNavigate } from "react-router-dom"; import { DocumentPreviewModal } from "@/components/ui/DocumentPreviewModal"; @@ -56,9 +56,9 @@ interface FnFDetailsProps { } const ALL_DEPARTMENTS = [ - 'Warranty Department', 'Accessories Department', 'Sales Department', 'RTO Department', - 'Service Department', 'Parts Department', 'Finance Department', 'Insurance Department', - 'Inventory Department', 'Marketing Department', 'HR Department', 'IT Department', + 'Warranty Department', 'Accessories Department', 'Sales Department', 'RTO Department', + 'Service Department', 'Parts Department', 'Finance Department', 'Insurance Department', + 'Inventory Department', 'Marketing Department', 'HR Department', 'IT Department', 'Legal Department', 'Quality Department', 'Logistics Department', 'Customer Relations Department' ]; @@ -87,7 +87,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { }); const [clearanceFile, setClearanceFile] = useState(null); - + useEffect(() => { fetchFnFDetails(); fetchAuditLogs(); @@ -96,7 +96,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { const normalizeDepartment = (name: string) => { if (!name) return name; let inputName = name.trim(); - + // Exact match first const exactMatch = ALL_DEPARTMENTS.find(d => d.toLowerCase() === inputName.toLowerCase()); if (exactMatch) return exactMatch; @@ -216,9 +216,9 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { mappedCase.netAmount = mappedCase.totalPayableAmount - mappedCase.totalRecoveryAmount - mappedCase.totalDeductions; mappedCase.departmentResponses = [ - 'Warranty Department', 'Accessories Department', 'Sales Department', 'RTO Department', - 'Service Department', 'Parts Department', 'Finance Department', 'Insurance Department', - 'Inventory Department', 'Marketing Department', 'HR Department', 'IT Department', + 'Warranty Department', 'Accessories Department', 'Sales Department', 'RTO Department', + 'Service Department', 'Parts Department', 'Finance Department', 'Insurance Department', + 'Inventory Department', 'Marketing Department', 'HR Department', 'IT Department', 'Legal Department', 'Quality Department', 'Logistics Department', 'Customer Relations Department' ].map((deptName: string) => { const c = (s.clearances || []).find( @@ -253,8 +253,8 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { const duesFlow = netAmount > 0 ? ("payable" as const) - : netAmount < 0 ? ("recovery" as const) - : null; + : netAmount < 0 ? ("recovery" as const) + : null; return { id: c?.id || `dept-${deptName}`, @@ -304,7 +304,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { participants: s.participants || [] }; setFnfCase(finalMapped); - + // Sync bank details from the pre-fetched data const preFetchedBankDetails = s.bankDetails || s.dealer?.bankDetails || s.outlet?.dealer?.dealerProfile?.bankDetails; if (preFetchedBankDetails && preFetchedBankDetails.length > 0) { @@ -349,7 +349,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { e.preventDefault(); const formData = new FormData(e.currentTarget); const data = Object.fromEntries(formData.entries()); - + try { setIsSubmittingBank(true); const dealerId = fnfCase?.outlet?.dealer?.id || fnfCase?.dealerId; @@ -357,13 +357,13 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { toast.error("Dealer information missing"); return; } - + const response = await API.saveBankDetail(dealerId, { ...data, id: editingBank?.id, isPrimary: formData.get("isPrimary") === "on", }) as any; - + if (response.data.success) { toast.success("Bank details saved successfully"); fetchBankDetails(dealerId); @@ -429,22 +429,30 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { currentUser.role, ); - const canRespondToDepartment = (departmentName: string) => { + const canRespondToDepartment = (dept: any) => { + if (!fnfCase || !dept) return false; const role = String(currentUser?.role || "").toLowerCase(); if (!role) return false; - const isGlobalResponder = - role.includes("super admin") || - role.includes("finance") || - role.includes("dd admin"); - if (isGlobalResponder) return true; + // 1. If any user (including Admin) has already responded, hide the button to prevent double-submission + const hasAlreadyResponded = dept.status !== "Pending"; + if (hasAlreadyResponded) return false; - const deptKeyword = departmentName.replace(" Department", "").toLowerCase(); + // 2. Case Level Closure: Finance Approval or Completed states lock the window + const isWindowClosed = ["Finance Approval", "Completed"].includes(fnfCase.status); + const hasOverridePower = role.includes("super admin") || role.includes("finance") || role.includes("dd admin"); + + if (isWindowClosed && !hasOverridePower) return false; + + // 3. Normal Role Check + if (hasOverridePower) return true; + + const deptKeyword = dept.departmentName.replace(" Department", "").toLowerCase(); return role.includes(deptKeyword); }; const canAnyDepartmentRespond = (fnfCase?.departmentResponses || []).some((dept: any) => - canRespondToDepartment(dept.departmentName), + canRespondToDepartment(dept), ); const handleUpdateClearance = async () => { @@ -463,7 +471,13 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { formData.append("type", clearanceForm.type); if (clearanceFile) formData.append("file", clearanceFile); - await API.updateFnFClearance(fnfId, selectedDept.clearanceId, formData); + const response = await API.updateFnFClearance(fnfId, selectedDept.clearanceId, formData) as any; + + if (!response.ok) { + toast.error(response.data?.message || "Failed to update department clearance"); + setIsUpdatingClearance(false); + return; + } toast.success(`Clearance updated for ${selectedDept.departmentName}`); setShowClearanceDialog(false); @@ -546,11 +560,10 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { return (
{/* Net Balance Banner */} - (fnfCase.totalPayableAmount || 0) + (fnfCase.totalPayableAmount || 0) ? "from-red-600 to-red-500" : "from-green-600 to-green-500" - } text-white`}> + } text-white`}>
@@ -617,7 +630,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { View Work Notes - {canSendToStakeholders && fnfCase.status === "New" && ( + {/* {canSendToStakeholders && fnfCase.status === "New" && ( - )} + )} */}
@@ -762,21 +775,20 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
0 ? "bg-amber-100 border-amber-600" : "bg-slate-100 border-slate-300" - }`} + }`} > {responsesReceived === totalDepartments || - ["Finance Approval", "Completed"].includes( - fnfCase.status, - ) ? ( + ["Finance Approval", "Completed"].includes( + fnfCase.status, + ) ? ( ) : responsesReceived > 0 ? ( @@ -785,14 +797,13 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { )}
@@ -804,9 +815,9 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { 0 ? "bg-amber-600" @@ -814,9 +825,9 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { } > {responsesReceived === totalDepartments || - ["Finance Approval", "Completed"].includes( - fnfCase.status, - ) + ["Finance Approval", "Completed"].includes( + fnfCase.status, + ) ? "Completed" : responsesReceived > 0 ? "In Progress" @@ -834,9 +845,9 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
{fnfCase.status === "Completed" ? ( @@ -919,11 +929,10 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { )}
@@ -957,58 +966,58 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { {["Finance Approval", "Completed"].includes( fnfCase.status, ) && ( - - -
-
-

- Payable Amount -

-

- ₹ - {fnfCase.totalPayableAmount?.toLocaleString() || - "0"} -

-
-
-

- Receivable amount -

-

- ₹ - {fnfCase.totalRecoveryAmount?.toLocaleString() || - "0"} -

-
-
-

- Net Amount -

-

- (fnfCase.totalPayableAmount || 0) - ? "text-red-900" - : "text-green-900" - } - > - ₹ - {Math.abs( - (fnfCase.totalRecoveryAmount || 0) - + + +

+
+

+ Payable Amount +

+

+ ₹ + {fnfCase.totalPayableAmount?.toLocaleString() || + "0"} +

+
+
+

+ Receivable amount +

+

+ ₹ + {fnfCase.totalRecoveryAmount?.toLocaleString() || + "0"} +

+
+
+

+ Net Amount +

+

+ (fnfCase.totalPayableAmount || 0) + ? "text-red-900" + : "text-green-900" + } + > + ₹ + {Math.abs( + (fnfCase.totalRecoveryAmount || 0) - (fnfCase.totalPayableAmount || 0), - ).toLocaleString()} -

+ ).toLocaleString()} +

+
-
- - - )} + + + )}
@@ -1016,13 +1025,12 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
{fnfCase.status === "Completed" ? ( @@ -1033,11 +1041,10 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { )}
@@ -1095,11 +1102,10 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
{fnfCase.status === "Completed" ? ( @@ -1108,11 +1114,10 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { )}
@@ -1146,11 +1151,10 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
{fnfCase.status === "Completed" ? ( @@ -1407,11 +1411,10 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { {dept.amount ? ( ₹{dept.amount.toLocaleString()} @@ -1425,7 +1428,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) { {canAnyDepartmentRespond && ( - {canRespondToDepartment(dept.departmentName) ? ( + {canRespondToDepartment(dept) ? (
-
- +

Account Holder

@@ -1730,9 +1732,9 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
- - - )} -
- + {/* All Cases Tab */}
@@ -284,18 +284,16 @@ export function FnFPage({ currentUser, onViewDetails }: FnFPageProps) {
-
- +
+
@@ -328,9 +326,9 @@ export function FnFPage({ currentUser, onViewDetails }: FnFPageProps) {
- {canSendToStakeholders && fnfCase.status === 'Initiated' && ( - - )} -
- + {/* Clearance Tab */}
@@ -395,8 +393,8 @@ export function FnFPage({ currentUser, onViewDetails }: FnFPageProps) {
-
- + {/* Finance Approval Tab */}
@@ -459,8 +457,8 @@ export function FnFPage({ currentUser, onViewDetails }: FnFPageProps) {
-
- + {/* Completed Tab */}
@@ -519,8 +517,8 @@ export function FnFPage({ currentUser, onViewDetails }: FnFPageProps) {
- - -
-
- ) : ( -
-
-
-
- - -
-
- - -
-
-
- - setUploadFile(e.target.files ? e.target.files[0] : null)} /> -
-
-
- - -
-
- )} - - - setShowPreviewModal(false)} document={previewDoc} /> - - - -
-
- - Finalize FDD Audit - You are about to submit your final findings. This action will lock the audit session and trigger the LOI approval workflow. - -
- {(currentUser?.role !== 'FDD' && currentUser?.roleCode !== 'FDD') && ( -
- -
- {['Recommended', 'Qualified with Observations', 'Not Recommended'].map((rec) => ( - - ))} -
-
- )} -
- -