/** * Dealer Claim IO Tab * * This component handles IO (Internal Order) management for dealer claims. * Located in: src/dealer-claim/components/request-detail/ */ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { DollarSign, Download, CircleCheckBig, Target } from 'lucide-react'; import { toast } from 'sonner'; import { validateIO, updateIODetails, getClaimDetails } from '@/services/dealerClaimApi'; import { useAuth } from '@/contexts/AuthContext'; interface IOTabProps { request: any; apiRequest?: any; onRefresh?: () => void; } interface IOBlockedDetails { ioNumber: string; blockedAmount: number; availableBalance: number; // Available amount before block remainingBalance: number; // Remaining amount after block blockedDate: string; blockedBy: string; // User who blocked sapDocumentNumber: string; status: 'blocked' | 'released' | 'failed'; } export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) { const { user } = useAuth(); const requestId = apiRequest?.requestId || request?.requestId; // Load existing IO data from apiRequest or request const internalOrder = apiRequest?.internalOrder || apiRequest?.internal_order || null; const existingIONumber = internalOrder?.ioNumber || internalOrder?.io_number || request?.ioNumber || ''; const existingBlockedAmount = internalOrder?.ioBlockedAmount || internalOrder?.io_blocked_amount || 0; const existingAvailableBalance = internalOrder?.ioAvailableBalance || internalOrder?.io_available_balance || 0; const existingRemainingBalance = internalOrder?.ioRemainingBalance || internalOrder?.io_remaining_balance || 0; const sapDocNumber = internalOrder?.sapDocumentNumber || internalOrder?.sap_document_number || ''; // Get organizer user object from association (organizer) or fallback to organizedBy UUID const organizer = internalOrder?.organizer || null; const [ioNumber, setIoNumber] = useState(existingIONumber); const [fetchingAmount, setFetchingAmount] = useState(false); const [fetchedAmount, setFetchedAmount] = useState(null); const [amountToBlock, setAmountToBlock] = useState(''); const [blockedDetails, setBlockedDetails] = useState(null); const [blockingBudget, setBlockingBudget] = useState(false); // Load existing IO block details from apiRequest useEffect(() => { if (internalOrder && existingIONumber && existingBlockedAmount > 0) { const availableBeforeBlock = Number(existingAvailableBalance) + Number(existingBlockedAmount) || Number(existingAvailableBalance); // Get blocked by user name from organizer association (who blocked the amount) // When amount is blocked, organizedBy stores the user who blocked it const blockedByName = organizer?.displayName || organizer?.display_name || organizer?.name || (organizer?.firstName && organizer?.lastName ? `${organizer.firstName} ${organizer.lastName}`.trim() : null) || organizer?.email || 'Unknown User'; setBlockedDetails({ ioNumber: existingIONumber, blockedAmount: Number(existingBlockedAmount) || 0, availableBalance: availableBeforeBlock, // Available amount before block remainingBalance: Number(existingRemainingBalance) || Number(existingAvailableBalance), blockedDate: internalOrder.organizedAt || internalOrder.organized_at || new Date().toISOString(), blockedBy: blockedByName, sapDocumentNumber: sapDocNumber, status: (internalOrder.status === 'BLOCKED' ? 'blocked' : internalOrder.status === 'RELEASED' ? 'released' : 'blocked') as 'blocked' | 'released' | 'failed', }); setIoNumber(existingIONumber); // Set fetched amount if available balance exists if (availableBeforeBlock > 0) { setFetchedAmount(availableBeforeBlock); } } }, [internalOrder, existingIONumber, existingBlockedAmount, existingAvailableBalance, existingRemainingBalance, sapDocNumber, organizer]); /** * Fetch available budget from SAP * Validates IO number and gets available balance (returns dummy data for now) * Does not store anything in database - only validates */ const handleFetchAmount = async () => { if (!ioNumber.trim()) { toast.error('Please enter an IO number'); return; } if (!requestId) { toast.error('Request ID not found'); return; } setFetchingAmount(true); try { // Call validate IO endpoint - returns dummy data for now, will integrate with SAP later const ioData = await validateIO(requestId, ioNumber.trim()); if (ioData.isValid && ioData.availableBalance > 0) { setFetchedAmount(ioData.availableBalance); // Pre-fill amount to block with available balance setAmountToBlock(String(ioData.availableBalance)); toast.success(`IO fetched from SAP. Available balance: ₹${ioData.availableBalance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`); } else { toast.error('Invalid IO number or no available balance found'); setFetchedAmount(null); setAmountToBlock(''); } } catch (error: any) { console.error('Failed to fetch IO budget:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to validate IO number or fetch budget from SAP'; toast.error(errorMessage); setFetchedAmount(null); } finally { setFetchingAmount(false); } }; /** * Block budget in SAP system */ const handleBlockBudget = async () => { if (!ioNumber.trim() || fetchedAmount === null) { toast.error('Please fetch IO amount first'); return; } if (!requestId) { toast.error('Request ID not found'); return; } const blockAmount = parseFloat(amountToBlock); if (!amountToBlock || isNaN(blockAmount) || blockAmount <= 0) { toast.error('Please enter a valid amount to block'); return; } if (blockAmount > fetchedAmount) { toast.error('Amount to block exceeds available IO budget'); return; } setBlockingBudget(true); try { // Call updateIODetails with blockedAmount to block budget in SAP and store in database // This will store in internal_orders and claim_budget_tracking tables await updateIODetails(requestId, { ioNumber: ioNumber.trim(), ioAvailableBalance: fetchedAmount, ioBlockedAmount: blockAmount, ioRemainingBalance: fetchedAmount - blockAmount, }); // Fetch updated claim details to get the blocked IO data const claimData = await getClaimDetails(requestId); const updatedInternalOrder = claimData?.internalOrder || claimData?.internal_order; if (updatedInternalOrder) { const currentUser = user as any; // When blocking, always use the current user who is performing the block action // The organizer association may be from initial IO organization, but we want who blocked the amount const blockedByName = currentUser?.displayName || currentUser?.display_name || currentUser?.name || (currentUser?.firstName && currentUser?.lastName ? `${currentUser.firstName} ${currentUser.lastName}`.trim() : null) || currentUser?.email || 'Current User'; const blocked: IOBlockedDetails = { ioNumber: updatedInternalOrder.ioNumber || updatedInternalOrder.io_number || ioNumber, blockedAmount: Number(updatedInternalOrder.ioBlockedAmount || updatedInternalOrder.io_blocked_amount || blockAmount), availableBalance: fetchedAmount, // Available amount before block remainingBalance: Number(updatedInternalOrder.ioRemainingBalance || updatedInternalOrder.io_remaining_balance || (fetchedAmount - blockAmount)), blockedDate: updatedInternalOrder.organizedAt || updatedInternalOrder.organized_at || new Date().toISOString(), blockedBy: blockedByName, sapDocumentNumber: updatedInternalOrder.sapDocumentNumber || updatedInternalOrder.sap_document_number || '', status: 'blocked', }; setBlockedDetails(blocked); setAmountToBlock(''); // Clear the input toast.success('IO budget blocked successfully in SAP'); // Refresh request details onRefresh?.(); } else { toast.error('IO blocked but failed to fetch updated details'); onRefresh?.(); } } catch (error: any) { console.error('Failed to block IO budget:', error); const errorMessage = error?.response?.data?.message || error?.message || 'Failed to block IO budget in SAP'; toast.error(errorMessage); } finally { setBlockingBudget(false); } }; return (
{/* IO Budget Management Card */} IO Budget Management Enter IO number to fetch available budget from SAP {/* IO Number Input */}
setIoNumber(e.target.value)} disabled={fetchingAmount || !!blockedDetails} className="flex-1" />
{/* Fetched Amount Display */} {fetchedAmount !== null && !blockedDetails && ( <>

Available Amount

₹{fetchedAmount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

IO Number: {ioNumber}

Fetched from: SAP System

{/* Amount to Block Input */}
setAmountToBlock(e.target.value)} className="pl-8" />
{/* Block Button */} )}
{/* IO Blocked Details Card */} IO Blocked Details Details of IO blocked in SAP system {blockedDetails ? (
{/* Success Banner */}

IO Blocked Successfully

Budget has been reserved in SAP system

{/* Blocked Details */}

IO Number

{blockedDetails.ioNumber}

SAP Document Number

{blockedDetails.sapDocumentNumber || 'N/A'}

Blocked Amount

₹{blockedDetails.blockedAmount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

Available Amount (Before Block)

₹{blockedDetails.availableBalance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

Remaining Amount (After Block)

₹{blockedDetails.remainingBalance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

Blocked By

{blockedDetails.blockedBy}

Blocked At

{new Date(blockedDetails.blockedDate).toLocaleString('en-IN', { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true })}

Status

Blocked
) : (

No IO blocked yet

Enter IO number and fetch amount to block budget

)}
); }