multi iteration flow added for re-quotation and multiple io block feature added
This commit is contained in:
parent
e11f13d248
commit
6d6b2a3f9c
@ -5,7 +5,7 @@
|
|||||||
* Located in: src/dealer-claim/components/request-detail/
|
* Located in: src/dealer-claim/components/request-detail/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@ -30,7 +30,7 @@ interface IOBlockedDetails {
|
|||||||
blockedDate: string;
|
blockedDate: string;
|
||||||
blockedBy: string; // User who blocked
|
blockedBy: string; // User who blocked
|
||||||
sapDocumentNumber: string;
|
sapDocumentNumber: string;
|
||||||
status: 'blocked' | 'released' | 'failed';
|
status: 'blocked' | 'released' | 'failed' | 'pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
||||||
@ -38,18 +38,39 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
const requestId = apiRequest?.requestId || request?.requestId;
|
const requestId = apiRequest?.requestId || request?.requestId;
|
||||||
|
|
||||||
// Get organizer user object from association (organizer) or fallback to organizedBy UUID
|
// Get organizer user object from association (organizer) or fallback to organizedBy UUID
|
||||||
|
|
||||||
// Get estimated budget from proposal details or DealerClaimDetails
|
|
||||||
const proposalDetails = apiRequest?.proposalDetails || {};
|
const proposalDetails = apiRequest?.proposalDetails || {};
|
||||||
const claimDetails = apiRequest?.claimDetails || apiRequest || {};
|
const claimDetails = apiRequest?.claimDetails || apiRequest || {};
|
||||||
|
|
||||||
// Use totalProposedTaxableAmount if available (for Scenario 2), fallback to estimated budget
|
// Calculate total base amount (needed for budget verification as requested)
|
||||||
const estimatedBudget = Number(claimDetails?.totalProposedTaxableAmount || proposalDetails?.totalEstimatedBudget || proposalDetails?.total_estimated_budget || 0);
|
// This is the taxable amount excluding GST
|
||||||
|
const totalBaseAmount = useMemo(() => {
|
||||||
|
const costBreakupRaw = proposalDetails?.costBreakup || claimDetails?.costBreakup || [];
|
||||||
|
const costBreakup = Array.isArray(costBreakupRaw)
|
||||||
|
? costBreakupRaw
|
||||||
|
: (typeof costBreakupRaw === 'string'
|
||||||
|
? JSON.parse(costBreakupRaw)
|
||||||
|
: []);
|
||||||
|
|
||||||
|
if (!Array.isArray(costBreakup) || costBreakup.length === 0) {
|
||||||
|
return Number(claimDetails?.totalProposedTaxableAmount || proposalDetails?.totalEstimatedBudget || proposalDetails?.total_estimated_budget || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return costBreakup.reduce((sum: number, item: any) => {
|
||||||
|
const amount = typeof item === 'object' ? (item.amount || 0) : 0;
|
||||||
|
const quantity = typeof item === 'object' ? (item.quantity || 1) : 1;
|
||||||
|
return sum + (Number(amount) * Number(quantity));
|
||||||
|
}, 0);
|
||||||
|
}, [proposalDetails?.costBreakup, claimDetails?.costBreakup, claimDetails?.totalProposedTaxableAmount, proposalDetails?.totalEstimatedBudget]);
|
||||||
|
|
||||||
|
// Use base amount as the target budget for blocking
|
||||||
|
const estimatedBudget = totalBaseAmount;
|
||||||
|
|
||||||
// Budget status for signaling (Scenario 2)
|
// Budget status for signaling (Scenario 2)
|
||||||
const budgetTracking = apiRequest?.budgetTracking || apiRequest?.budget_tracking || {};
|
// Use apiRequest as the primary source of truth, fall back to request
|
||||||
|
const budgetTracking = apiRequest?.budgetTracking || request?.budgetTracking || {};
|
||||||
const budgetStatus = budgetTracking?.budgetStatus || budgetTracking?.budget_status || '';
|
const budgetStatus = budgetTracking?.budgetStatus || budgetTracking?.budget_status || '';
|
||||||
const isAdditionalBlockingNeeded = budgetStatus === 'PROPOSED' && (apiRequest?.internalOrders?.length > 0 || apiRequest?.internal_orders?.length > 0);
|
const internalOrdersList = apiRequest?.internalOrders || apiRequest?.internal_orders || request?.internalOrders || [];
|
||||||
|
const isAdditionalBlockingNeeded = budgetStatus === 'PROPOSED' && internalOrdersList.length > 0;
|
||||||
|
|
||||||
const [ioNumber, setIoNumber] = useState('');
|
const [ioNumber, setIoNumber] = useState('');
|
||||||
const [fetchingAmount, setFetchingAmount] = useState(false);
|
const [fetchingAmount, setFetchingAmount] = useState(false);
|
||||||
@ -60,9 +81,8 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
|
|
||||||
// Load existing IO blocks
|
// Load existing IO blocks
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ios = apiRequest?.internalOrders || apiRequest?.internal_orders || [];
|
if (internalOrdersList.length > 0) {
|
||||||
if (ios.length > 0) {
|
const formattedIOs = internalOrdersList.map((io: any) => {
|
||||||
const formattedIOs = ios.map((io: any) => {
|
|
||||||
const org = io.organizer || null;
|
const org = io.organizer || null;
|
||||||
const blockedByName = org?.displayName ||
|
const blockedByName = org?.displayName ||
|
||||||
org?.display_name ||
|
org?.display_name ||
|
||||||
@ -79,7 +99,8 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
blockedBy: blockedByName,
|
blockedBy: blockedByName,
|
||||||
sapDocumentNumber: io.sapDocumentNumber || io.sap_document_number || '',
|
sapDocumentNumber: io.sapDocumentNumber || io.sap_document_number || '',
|
||||||
status: (io.status === 'BLOCKED' ? 'blocked' :
|
status: (io.status === 'BLOCKED' ? 'blocked' :
|
||||||
io.status === 'RELEASED' ? 'released' : 'blocked') as 'blocked' | 'released' | 'failed',
|
io.status === 'RELEASED' ? 'released' :
|
||||||
|
io.status === 'PENDING' ? 'pending' : 'blocked') as any,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setBlockedIOs(formattedIOs);
|
setBlockedIOs(formattedIOs);
|
||||||
@ -89,7 +110,7 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
setIoNumber(formattedIOs[formattedIOs.length - 1].ioNumber);
|
setIoNumber(formattedIOs[formattedIOs.length - 1].ioNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [apiRequest, isAdditionalBlockingNeeded]);
|
}, [apiRequest, request, isAdditionalBlockingNeeded, internalOrdersList]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch available budget from SAP
|
* Fetch available budget from SAP
|
||||||
@ -114,12 +135,22 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
|
|
||||||
if (ioData.isValid && ioData.availableBalance > 0) {
|
if (ioData.isValid && ioData.availableBalance > 0) {
|
||||||
setFetchedAmount(ioData.availableBalance);
|
setFetchedAmount(ioData.availableBalance);
|
||||||
// Pre-fill amount to block with estimated budget (if available), otherwise use available balance
|
|
||||||
if (estimatedBudget > 0) {
|
// Calculate total already blocked amount
|
||||||
setAmountToBlock(String(estimatedBudget));
|
const totalAlreadyBlocked = blockedIOs.reduce((sum, io) => sum + io.blockedAmount, 0);
|
||||||
|
|
||||||
|
// Calculate remaining budget to block
|
||||||
|
const remainingToBlock = Math.max(0, estimatedBudget - totalAlreadyBlocked);
|
||||||
|
|
||||||
|
// Pre-fill amount to block with remaining budget, otherwise use available balance
|
||||||
|
if (remainingToBlock > 0) {
|
||||||
|
setAmountToBlock(String(remainingToBlock.toFixed(2)));
|
||||||
|
} else if (estimatedBudget > 0 && totalAlreadyBlocked === 0) {
|
||||||
|
setAmountToBlock(String(estimatedBudget.toFixed(2)));
|
||||||
} else {
|
} else {
|
||||||
setAmountToBlock(String(ioData.availableBalance));
|
setAmountToBlock(String(ioData.availableBalance.toFixed(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success(`IO fetched from SAP. Available balance: ₹${ioData.availableBalance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`);
|
toast.success(`IO fetched from SAP. Available balance: ₹${ioData.availableBalance.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`);
|
||||||
} else {
|
} else {
|
||||||
toast.error('Invalid IO number or no available balance found');
|
toast.error('Invalid IO number or no available balance found');
|
||||||
@ -417,8 +448,13 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
<div key={idx} className="border rounded-lg overflow-hidden">
|
<div key={idx} className="border rounded-lg overflow-hidden">
|
||||||
<div className={`p-3 flex justify-between items-center ${idx === 0 ? 'bg-green-50' : 'bg-gray-50'}`}>
|
<div className={`p-3 flex justify-between items-center ${idx === 0 ? 'bg-green-50' : 'bg-gray-50'}`}>
|
||||||
<span className="font-semibold text-sm">IO: {io.ioNumber}</span>
|
<span className="font-semibold text-sm">IO: {io.ioNumber}</span>
|
||||||
<Badge className={io.status === 'blocked' ? 'bg-green-100 text-green-800' : 'bg-blue-100 text-blue-800'}>
|
<Badge className={
|
||||||
{io.status === 'blocked' ? 'Blocked' : 'Released'}
|
io.status === 'blocked' ? 'bg-green-100 text-green-800' :
|
||||||
|
io.status === 'pending' ? 'bg-amber-100 text-amber-800' :
|
||||||
|
'bg-blue-100 text-blue-800'
|
||||||
|
}>
|
||||||
|
{io.status === 'blocked' ? 'Blocked' :
|
||||||
|
io.status === 'pending' ? 'Provisioned' : 'Released'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 divide-x divide-y">
|
<div className="grid grid-cols-2 divide-x divide-y">
|
||||||
|
|||||||
@ -49,7 +49,7 @@ interface WorkflowStep {
|
|||||||
approver: string;
|
approver: string;
|
||||||
description: string;
|
description: string;
|
||||||
tatHours: number;
|
tatHours: number;
|
||||||
status: 'pending' | 'approved' | 'waiting' | 'rejected' | 'in_progress';
|
status: 'pending' | 'approved' | 'waiting' | 'rejected' | 'in_progress' | 'skipped';
|
||||||
comment?: string;
|
comment?: string;
|
||||||
approvedAt?: string;
|
approvedAt?: string;
|
||||||
elapsedHours?: number;
|
elapsedHours?: number;
|
||||||
@ -109,6 +109,8 @@ const getStepIcon = (status: string) => {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'approved':
|
case 'approved':
|
||||||
return <CircleCheckBig className="w-5 h-5 text-green-600" />;
|
return <CircleCheckBig className="w-5 h-5 text-green-600" />;
|
||||||
|
case 'skipped':
|
||||||
|
return <CheckCircle className="w-5 h-5 text-green-600" />;
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return <Clock className="w-5 h-5 text-blue-600" />;
|
return <Clock className="w-5 h-5 text-blue-600" />;
|
||||||
case 'rejected':
|
case 'rejected':
|
||||||
@ -125,6 +127,8 @@ const getStepBadgeVariant = (status: string) => {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'approved':
|
case 'approved':
|
||||||
return 'bg-green-100 text-green-800 border-green-200';
|
return 'bg-green-100 text-green-800 border-green-200';
|
||||||
|
case 'skipped':
|
||||||
|
return 'bg-green-50 text-green-700 border-green-200';
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return 'bg-purple-100 text-purple-800 border-purple-200';
|
return 'bg-purple-100 text-purple-800 border-purple-200';
|
||||||
case 'rejected':
|
case 'rejected':
|
||||||
@ -141,7 +145,7 @@ const getStepCardStyle = (status: string, isActive: boolean) => {
|
|||||||
if (isActive && (status === 'pending' || status === 'in_progress')) {
|
if (isActive && (status === 'pending' || status === 'in_progress')) {
|
||||||
return 'border-purple-500 bg-purple-50 shadow-md';
|
return 'border-purple-500 bg-purple-50 shadow-md';
|
||||||
}
|
}
|
||||||
if (status === 'approved') {
|
if (status === 'approved' || status === 'skipped') {
|
||||||
return 'border-green-500 bg-green-50';
|
return 'border-green-500 bg-green-50';
|
||||||
}
|
}
|
||||||
if (status === 'rejected') {
|
if (status === 'rejected') {
|
||||||
@ -157,6 +161,8 @@ const getStepIconBg = (status: string) => {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'approved':
|
case 'approved':
|
||||||
return 'bg-green-100';
|
return 'bg-green-100';
|
||||||
|
case 'skipped':
|
||||||
|
return 'bg-green-100';
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return 'bg-purple-100';
|
return 'bg-purple-100';
|
||||||
case 'rejected':
|
case 'rejected':
|
||||||
@ -670,6 +676,8 @@ export function DealerClaimWorkflowTab({
|
|||||||
const approvalStatus = approval.status.toLowerCase();
|
const approvalStatus = approval.status.toLowerCase();
|
||||||
if (approvalStatus === 'approved') {
|
if (approvalStatus === 'approved') {
|
||||||
normalizedStatus = 'approved';
|
normalizedStatus = 'approved';
|
||||||
|
} else if (approvalStatus === 'skipped') {
|
||||||
|
normalizedStatus = 'skipped';
|
||||||
} else if (approvalStatus === 'rejected') {
|
} else if (approvalStatus === 'rejected') {
|
||||||
normalizedStatus = 'rejected';
|
normalizedStatus = 'rejected';
|
||||||
} else {
|
} else {
|
||||||
@ -1675,7 +1683,7 @@ export function DealerClaimWorkflowTab({
|
|||||||
// - AND it matches the current step
|
// - AND it matches the current step
|
||||||
// - AND is pending/in_progress
|
// - AND is pending/in_progress
|
||||||
const isActive = isRequestActive && isPendingOrInProgress && matchesCurrentStep;
|
const isActive = isRequestActive && isPendingOrInProgress && matchesCurrentStep;
|
||||||
const isCompleted = step.status === 'approved';
|
const isCompleted = step.status === 'approved' || step.status === 'skipped';
|
||||||
|
|
||||||
// Find approval data for this step to get SLA information
|
// Find approval data for this step to get SLA information
|
||||||
// First find the corresponding level in approvalFlow to get levelId
|
// First find the corresponding level in approvalFlow to get levelId
|
||||||
@ -2527,6 +2535,7 @@ export function DealerClaimWorkflowTab({
|
|||||||
previousProposalData={versionHistory?.find(v => v.snapshotType === 'PROPOSAL')?.snapshotData}
|
previousProposalData={versionHistory?.find(v => v.snapshotType === 'PROPOSAL')?.snapshotData}
|
||||||
documentPolicy={documentPolicy}
|
documentPolicy={documentPolicy}
|
||||||
taxationType={request?.claimDetails?.taxationType}
|
taxationType={request?.claimDetails?.taxationType}
|
||||||
|
totalBlockedAmount={(request?.internalOrders || []).reduce((sum: number, io: any) => sum + (Number(io.ioBlockedAmount || io.io_blocked_amount || io.blockedAmount || 0)), 0)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Initiator Proposal Approval Modal */}
|
{/* Initiator Proposal Approval Modal */}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ interface CostItem {
|
|||||||
cessAmt: number;
|
cessAmt: number;
|
||||||
totalAmt: number;
|
totalAmt: number;
|
||||||
isService: boolean;
|
isService: boolean;
|
||||||
|
isOriginal?: boolean; // Flag to identify items from previous iterations
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DealerProposalSubmissionModalProps {
|
interface DealerProposalSubmissionModalProps {
|
||||||
@ -58,7 +59,10 @@ interface DealerProposalSubmissionModalProps {
|
|||||||
onSubmit: (data: {
|
onSubmit: (data: {
|
||||||
proposalDocument: File | null;
|
proposalDocument: File | null;
|
||||||
costBreakup: CostItem[];
|
costBreakup: CostItem[];
|
||||||
|
totalEstimatedBudget: number;
|
||||||
expectedCompletionDate: string;
|
expectedCompletionDate: string;
|
||||||
|
timelineMode: 'date' | 'days';
|
||||||
|
expectedCompletionDays: number;
|
||||||
otherDocuments: File[];
|
otherDocuments: File[];
|
||||||
dealerComments: string;
|
dealerComments: string;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
@ -73,6 +77,7 @@ interface DealerProposalSubmissionModalProps {
|
|||||||
allowedFileTypes: string[];
|
allowedFileTypes: string[];
|
||||||
};
|
};
|
||||||
taxationType?: string | null;
|
taxationType?: string | null;
|
||||||
|
totalBlockedAmount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DealerProposalSubmissionModal({
|
export function DealerProposalSubmissionModal({
|
||||||
@ -87,6 +92,7 @@ export function DealerProposalSubmissionModal({
|
|||||||
defaultGstRate = 18,
|
defaultGstRate = 18,
|
||||||
documentPolicy,
|
documentPolicy,
|
||||||
taxationType,
|
taxationType,
|
||||||
|
totalBlockedAmount = 0,
|
||||||
}: DealerProposalSubmissionModalProps) {
|
}: DealerProposalSubmissionModalProps) {
|
||||||
const [proposalDocument, setProposalDocument] = useState<File | null>(null);
|
const [proposalDocument, setProposalDocument] = useState<File | null>(null);
|
||||||
|
|
||||||
@ -237,7 +243,6 @@ export function DealerProposalSubmissionModal({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle download file (for non-previewable files)
|
|
||||||
const handleDownloadFile = (file: File) => {
|
const handleDownloadFile = (file: File) => {
|
||||||
const url = URL.createObjectURL(file);
|
const url = URL.createObjectURL(file);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@ -249,6 +254,71 @@ export function DealerProposalSubmissionModal({
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pre-populate with previous proposal data if available
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && previousProposalData) {
|
||||||
|
const isDeltaFlow = totalBlockedAmount > 0;
|
||||||
|
if (previousProposalData.costItems && previousProposalData.costItems.length > 0) {
|
||||||
|
const mappedItems: CostItem[] = previousProposalData.costItems.map((item: any, index: number) => ({
|
||||||
|
id: `original-${index}`,
|
||||||
|
description: item.itemDescription || item.description || '',
|
||||||
|
amount: Number(item.amount) || 0,
|
||||||
|
quantity: Number(item.quantity) || 1,
|
||||||
|
hsnCode: item.hsnCode || '',
|
||||||
|
isService: !!item.isService,
|
||||||
|
gstRate: Number(item.gstRate) || defaultGstRate,
|
||||||
|
cgstRate: Number(item.cgstRate) || 0,
|
||||||
|
sgstRate: Number(item.sgstRate) || 0,
|
||||||
|
utgstRate: Number(item.utgstRate) || 0,
|
||||||
|
igstRate: Number(item.igstRate) || 0,
|
||||||
|
gstAmt: Number(item.gstAmt) || 0,
|
||||||
|
cgstAmt: Number(item.cgstAmt) || 0,
|
||||||
|
sgstAmt: Number(item.sgstAmt) || 0,
|
||||||
|
utgstAmt: Number(item.utgstAmt) || 0,
|
||||||
|
igstAmt: Number(item.igstAmt) || 0,
|
||||||
|
cessRate: Number(item.cessRate) || 0,
|
||||||
|
cessAmt: Number(item.cessAmt) || 0,
|
||||||
|
totalAmt: Number(item.totalAmt) || 0,
|
||||||
|
isOriginal: isDeltaFlow // Only lock as original if budget is already being blocked
|
||||||
|
}));
|
||||||
|
setCostItems(mappedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousProposalData.expectedCompletionDate) {
|
||||||
|
setExpectedCompletionDate(previousProposalData.expectedCompletionDate.split('T')[0]);
|
||||||
|
setTimelineMode('date');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We DON'T pre-populate comments to force dealer to explain the re-quotation
|
||||||
|
// setDealerComments(previousProposalData.comments || '');
|
||||||
|
} else if (isOpen && !previousProposalData) {
|
||||||
|
// Default state for new proposals
|
||||||
|
setCostItems([{
|
||||||
|
id: '1',
|
||||||
|
description: '',
|
||||||
|
amount: 0,
|
||||||
|
gstRate: defaultGstRate || 0,
|
||||||
|
gstAmt: 0,
|
||||||
|
quantity: 1,
|
||||||
|
hsnCode: '',
|
||||||
|
isService: false,
|
||||||
|
cgstRate: 0,
|
||||||
|
cgstAmt: 0,
|
||||||
|
sgstRate: 0,
|
||||||
|
sgstAmt: 0,
|
||||||
|
igstRate: 0,
|
||||||
|
igstAmt: 0,
|
||||||
|
utgstRate: 0,
|
||||||
|
utgstAmt: 0,
|
||||||
|
cessRate: 0,
|
||||||
|
cessAmt: 0,
|
||||||
|
totalAmt: 0
|
||||||
|
}]);
|
||||||
|
setExpectedCompletionDate('');
|
||||||
|
setDealerComments('');
|
||||||
|
}
|
||||||
|
}, [isOpen, previousProposalData, defaultGstRate, totalBlockedAmount]);
|
||||||
|
|
||||||
// Calculate total estimated budget (inclusive of GST)
|
// Calculate total estimated budget (inclusive of GST)
|
||||||
const totalBudget = useMemo(() => {
|
const totalBudget = useMemo(() => {
|
||||||
return costItems.reduce((sum, item) => sum + (item.totalAmt || item.amount || 0), 0);
|
return costItems.reduce((sum, item) => sum + (item.totalAmt || item.amount || 0), 0);
|
||||||
@ -368,9 +438,9 @@ export function DealerProposalSubmissionModal({
|
|||||||
let updatedItem = { ...item, [field]: value };
|
let updatedItem = { ...item, [field]: value };
|
||||||
|
|
||||||
// Re-calculate GST if relevant fields change
|
// Re-calculate GST if relevant fields change
|
||||||
if (['amount', 'gstRate', 'cgstRate', 'sgstRate', 'utgstRate', 'igstRate', 'quantity'].includes(field)) {
|
if (['amount', 'gstRate', 'cgstRate', 'sgstRate', 'utgstRate', 'igstRate'].includes(field)) {
|
||||||
const amount = field === 'amount' ? parseFloat(value) || 0 : item.amount;
|
const amount = field === 'amount' ? parseFloat(value) || 0 : item.amount;
|
||||||
const quantity = field === 'quantity' ? parseInt(value) || 1 : item.quantity;
|
const quantity = 1; // Quantity is now fixed to 1
|
||||||
|
|
||||||
let cgstRate = item.cgstRate;
|
let cgstRate = item.cgstRate;
|
||||||
let sgstRate = item.sgstRate;
|
let sgstRate = item.sgstRate;
|
||||||
@ -481,10 +551,23 @@ export function DealerProposalSubmissionModal({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDeltaFlow = totalBlockedAmount > 0;
|
||||||
|
const costBreakup = costItems
|
||||||
|
.filter(item => item.description.trim() !== '' && item.amount > 0)
|
||||||
|
.map(item => ({
|
||||||
|
...item,
|
||||||
|
description: (previousProposalData && isDeltaFlow && !item.isOriginal)
|
||||||
|
? `[ADDITIONAL] ${item.description}`
|
||||||
|
: item.description
|
||||||
|
}));
|
||||||
|
|
||||||
await onSubmit({
|
await onSubmit({
|
||||||
proposalDocument,
|
proposalDocument,
|
||||||
costBreakup: costItems.filter(item => item.description.trim() !== '' && item.amount > 0),
|
costBreakup,
|
||||||
|
totalEstimatedBudget: totalBudget,
|
||||||
expectedCompletionDate: finalCompletionDate,
|
expectedCompletionDate: finalCompletionDate,
|
||||||
|
timelineMode,
|
||||||
|
expectedCompletionDays: timelineMode === 'days' ? parseInt(numberOfDays) : 0,
|
||||||
otherDocuments,
|
otherDocuments,
|
||||||
dealerComments,
|
dealerComments,
|
||||||
});
|
});
|
||||||
@ -691,7 +774,6 @@ export function DealerProposalSubmissionModal({
|
|||||||
<thead className="bg-gray-50 text-gray-600">
|
<thead className="bg-gray-50 text-gray-600">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-2 font-medium">Description</th>
|
<th className="p-2 font-medium">Description</th>
|
||||||
<th className="p-2 font-medium text-right">Qty</th>
|
|
||||||
<th className="p-2 font-medium text-right">Amount</th>
|
<th className="p-2 font-medium text-right">Amount</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -699,16 +781,13 @@ export function DealerProposalSubmissionModal({
|
|||||||
{(previousProposalData.costItems || previousProposalData.costBreakup).map((item: any, idx: number) => (
|
{(previousProposalData.costItems || previousProposalData.costBreakup).map((item: any, idx: number) => (
|
||||||
<tr key={idx} className="bg-white">
|
<tr key={idx} className="bg-white">
|
||||||
<td className="p-2 text-gray-800">{item.description}</td>
|
<td className="p-2 text-gray-800">{item.description}</td>
|
||||||
<td className="p-2 text-right text-gray-800 font-medium">
|
|
||||||
{item.quantity || 1}
|
|
||||||
</td>
|
|
||||||
<td className="p-2 text-right text-gray-800 font-medium">
|
<td className="p-2 text-right text-gray-800 font-medium">
|
||||||
₹{Number(item.amount).toLocaleString('en-IN')}
|
₹{Number(item.amount).toLocaleString('en-IN')}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
<tr className="bg-gray-50 font-bold">
|
<tr className="bg-gray-50 font-bold">
|
||||||
<td colSpan={2} className="p-2 text-gray-900">Total</td>
|
<td colSpan={1} className="p-2 text-gray-900">Total</td>
|
||||||
<td className="p-2 text-right text-gray-900">
|
<td className="p-2 text-right text-gray-900">
|
||||||
₹{Number(previousProposalData.totalEstimatedBudget || previousProposalData.totalBudget || 0).toLocaleString('en-IN')}
|
₹{Number(previousProposalData.totalEstimatedBudget || previousProposalData.totalBudget || 0).toLocaleString('en-IN')}
|
||||||
</td>
|
</td>
|
||||||
@ -853,12 +932,20 @@ export function DealerProposalSubmissionModal({
|
|||||||
{/* Row 1: Description and Close */}
|
{/* Row 1: Description and Close */}
|
||||||
<div className="flex gap-3 items-start">
|
<div className="flex gap-3 items-start">
|
||||||
<div className={`${isNonGst ? 'flex-[3]' : 'flex-1'}`}>
|
<div className={`${isNonGst ? 'flex-[3]' : 'flex-1'}`}>
|
||||||
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item Description</Label>
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
<Label className="text-[10px] uppercase text-gray-500 font-bold">Item Description</Label>
|
||||||
|
{item.isOriginal ? (
|
||||||
|
<Badge className="text-[9px] h-4 bg-gray-200 text-gray-700 hover:bg-gray-200 border-none">ORIGINAL</Badge>
|
||||||
|
) : (previousProposalData && totalBlockedAmount > 0) ? (
|
||||||
|
<Badge className="text-[9px] h-4 bg-amber-100 text-amber-700 hover:bg-amber-100 border-none">ADDITIONAL</Badge>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
<Input
|
<Input
|
||||||
placeholder="e.g., Venue branding, Logistics, etc."
|
placeholder="e.g., Venue branding, Logistics, etc."
|
||||||
value={item.description}
|
value={item.description}
|
||||||
onChange={(e) => handleCostItemChange(item.id, 'description', e.target.value)}
|
onChange={(e) => handleCostItemChange(item.id, 'description', e.target.value)}
|
||||||
className="w-full bg-white shadow-sm"
|
className={`w-full bg-white shadow-sm ${item.isOriginal ? 'bg-gray-100 cursor-not-allowed' : ''}`}
|
||||||
|
disabled={item.isOriginal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isNonGst && (
|
{isNonGst && (
|
||||||
@ -870,7 +957,8 @@ export function DealerProposalSubmissionModal({
|
|||||||
type="number"
|
type="number"
|
||||||
value={item.amount || ''}
|
value={item.amount || ''}
|
||||||
onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)}
|
onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)}
|
||||||
className="pl-8 bg-white shadow-sm"
|
className={`pl-8 bg-white shadow-sm ${item.isOriginal ? 'bg-gray-100 cursor-not-allowed' : ''}`}
|
||||||
|
disabled={item.isOriginal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -881,7 +969,7 @@ export function DealerProposalSubmissionModal({
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="mt-6 hover:bg-red-50 hover:text-red-600 h-9 w-9 p-0 rounded-full"
|
className="mt-6 hover:bg-red-50 hover:text-red-600 h-9 w-9 p-0 rounded-full"
|
||||||
onClick={() => handleRemoveCostItem(item.id)}
|
onClick={() => handleRemoveCostItem(item.id)}
|
||||||
disabled={costItems.length === 1}
|
disabled={costItems.length === 1 || item.isOriginal}
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4" />
|
<X className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -897,7 +985,8 @@ export function DealerProposalSubmissionModal({
|
|||||||
type="number"
|
type="number"
|
||||||
value={item.amount || ''}
|
value={item.amount || ''}
|
||||||
onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)}
|
onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)}
|
||||||
className="pl-8 bg-white shadow-sm"
|
className={`pl-8 bg-white shadow-sm ${item.isOriginal ? 'bg-gray-100 cursor-not-allowed' : ''}`}
|
||||||
|
disabled={item.isOriginal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -906,7 +995,8 @@ export function DealerProposalSubmissionModal({
|
|||||||
<Input
|
<Input
|
||||||
value={item.hsnCode || ''}
|
value={item.hsnCode || ''}
|
||||||
onChange={(e) => handleCostItemChange(item.id, 'hsnCode', e.target.value)}
|
onChange={(e) => handleCostItemChange(item.id, 'hsnCode', e.target.value)}
|
||||||
className={`bg-white shadow-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500' : ''}`}
|
className={`bg-white shadow-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500' : ''} ${item.isOriginal ? 'bg-gray-100 cursor-not-allowed' : ''}`}
|
||||||
|
disabled={item.isOriginal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-28 sm:w-32">
|
<div className="w-28 sm:w-32">
|
||||||
@ -914,7 +1004,8 @@ export function DealerProposalSubmissionModal({
|
|||||||
<select
|
<select
|
||||||
value={item.isService ? 'SAC' : 'HSN'}
|
value={item.isService ? 'SAC' : 'HSN'}
|
||||||
onChange={(e) => handleCostItemChange(item.id, 'isService', e.target.value === 'SAC')}
|
onChange={(e) => handleCostItemChange(item.id, 'isService', e.target.value === 'SAC')}
|
||||||
className="flex h-10 w-full rounded-md border border-input bg-white px-3 py-2 text-sm shadow-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-[#2d4a3e]"
|
className={`flex h-10 w-full rounded-md border border-input bg-white px-3 py-2 text-sm shadow-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-[#2d4a3e] ${item.isOriginal ? 'bg-gray-100 cursor-not-allowed' : ''}`}
|
||||||
|
disabled={item.isOriginal}
|
||||||
>
|
>
|
||||||
<option value="HSN">HSN (Goods)</option>
|
<option value="HSN">HSN (Goods)</option>
|
||||||
<option value="SAC">SAC (Service)</option>
|
<option value="SAC">SAC (Service)</option>
|
||||||
|
|||||||
@ -97,19 +97,7 @@ export function InitiatorProposalApprovalModal({
|
|||||||
const [actionType, setActionType] = useState<'approve' | 'reject' | 'revision' | null>(null);
|
const [actionType, setActionType] = useState<'approve' | 'reject' | 'revision' | null>(null);
|
||||||
const [showPreviousProposal, setShowPreviousProposal] = useState(false);
|
const [showPreviousProposal, setShowPreviousProposal] = useState(false);
|
||||||
|
|
||||||
// Check if IO is blocked (IO blocking moved to Requestor Evaluation level)
|
// Calculate total budget (needed for display)
|
||||||
const internalOrder = request?.internalOrder || request?.internal_order;
|
|
||||||
const ioBlockedAmount = internalOrder?.ioBlockedAmount || internalOrder?.io_blocked_amount || 0;
|
|
||||||
const isIOBlocked = ioBlockedAmount > 0;
|
|
||||||
const [previewDoc, setPreviewDoc] = useState<{
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
type?: string;
|
|
||||||
size?: number;
|
|
||||||
id?: string;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
// Calculate total budget
|
|
||||||
const totalBudget = useMemo(() => {
|
const totalBudget = useMemo(() => {
|
||||||
if (!proposalData?.costBreakup) return 0;
|
if (!proposalData?.costBreakup) return 0;
|
||||||
|
|
||||||
@ -132,6 +120,57 @@ export function InitiatorProposalApprovalModal({
|
|||||||
}, 0);
|
}, 0);
|
||||||
}, [proposalData]);
|
}, [proposalData]);
|
||||||
|
|
||||||
|
// Calculate total base amount (needed for budget verification as requested)
|
||||||
|
// This is the taxable amount excluding GST
|
||||||
|
const totalBaseAmount = useMemo(() => {
|
||||||
|
if (!proposalData?.costBreakup) return 0;
|
||||||
|
|
||||||
|
const costBreakup = Array.isArray(proposalData.costBreakup)
|
||||||
|
? proposalData.costBreakup
|
||||||
|
: (typeof proposalData.costBreakup === 'string'
|
||||||
|
? JSON.parse(proposalData.costBreakup)
|
||||||
|
: []);
|
||||||
|
|
||||||
|
if (!Array.isArray(costBreakup)) return 0;
|
||||||
|
|
||||||
|
return costBreakup.reduce((sum: number, item: any) => {
|
||||||
|
const amount = typeof item === 'object' ? (item.amount || 0) : 0;
|
||||||
|
const quantity = typeof item === 'object' ? (item.quantity || 1) : 1;
|
||||||
|
return sum + (Number(amount) * Number(quantity));
|
||||||
|
}, 0);
|
||||||
|
}, [proposalData]);
|
||||||
|
|
||||||
|
// Check if IO is blocked (IO blocking moved to Requestor Evaluation level)
|
||||||
|
// Sum up all successful blocks from internalOrders array
|
||||||
|
const totalBlockedAmount = useMemo(() => {
|
||||||
|
const internalOrders = request?.internalOrders || request?.internal_orders || [];
|
||||||
|
|
||||||
|
// If we have an array, sum the blocked amounts
|
||||||
|
if (Array.isArray(internalOrders) && internalOrders.length > 0) {
|
||||||
|
return internalOrders.reduce((sum: number, io: any) => {
|
||||||
|
const amt = Number(io.ioBlockedAmount || io.io_blocked_amount || 0);
|
||||||
|
return sum + amt;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to single internalOrder object for backward compatibility
|
||||||
|
const singleIO = request?.internalOrder || request?.internal_order;
|
||||||
|
return Number(singleIO?.ioBlockedAmount || singleIO?.io_blocked_amount || 0);
|
||||||
|
}, [request?.internalOrders, request?.internal_orders, request?.internalOrder, request?.internal_order]);
|
||||||
|
|
||||||
|
// Budget is considered blocked only if the total blocked amount matches or exceeds the proposed base amount
|
||||||
|
// Allow a small margin for floating point comparison if needed, but here simple >= should suffice
|
||||||
|
const isIOBlocked = totalBlockedAmount >= (totalBaseAmount - 0.01);
|
||||||
|
const remainingBaseToBlock = Math.max(0, totalBaseAmount - totalBlockedAmount);
|
||||||
|
|
||||||
|
const [previewDoc, setPreviewDoc] = useState<{
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
type?: string;
|
||||||
|
size?: number;
|
||||||
|
id?: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
// Format date
|
// Format date
|
||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string) => {
|
||||||
if (!dateString) return '—';
|
if (!dateString) return '—';
|
||||||
@ -632,7 +671,20 @@ export function InitiatorProposalApprovalModal({
|
|||||||
{costBreakup.map((item: any, index: number) => (
|
{costBreakup.map((item: any, index: number) => (
|
||||||
<div key={item?.id || item?.description || index} className={`px-3 lg:px-4 py-2 lg:py-3 grid ${isNonGst ? 'grid-cols-3' : 'grid-cols-4'} gap-4`}>
|
<div key={item?.id || item?.description || index} className={`px-3 lg:px-4 py-2 lg:py-3 grid ${isNonGst ? 'grid-cols-3' : 'grid-cols-4'} gap-4`}>
|
||||||
<div className="col-span-1 text-xs lg:text-sm text-gray-700">
|
<div className="col-span-1 text-xs lg:text-sm text-gray-700">
|
||||||
{item?.description || 'N/A'}
|
<div className="flex items-center gap-1.5 mb-0.5">
|
||||||
|
<span className="font-medium">
|
||||||
|
{item?.description?.startsWith('[ADDITIONAL]')
|
||||||
|
? item.description.replace('[ADDITIONAL]', '').trim()
|
||||||
|
: (item?.description || 'N/A')}
|
||||||
|
</span>
|
||||||
|
{costBreakup.some((i: any) => i?.description?.startsWith('[ADDITIONAL]')) && (
|
||||||
|
item?.description?.startsWith('[ADDITIONAL]') ? (
|
||||||
|
<Badge className="text-[9px] h-3.5 px-1 bg-amber-100 text-amber-700 hover:bg-amber-100 border-none leading-none">ADDITIONAL</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge className="text-[9px] h-3.5 px-1 bg-gray-100 text-gray-600 hover:bg-gray-100 border-none leading-none">ORIGINAL</Badge>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{!isNonGst && item?.gstRate ? <span className="block text-[10px] text-gray-400">{item.gstRate}% GST</span> : null}
|
{!isNonGst && item?.gstRate ? <span className="block text-[10px] text-gray-400">{item.gstRate}% GST</span> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs lg:text-sm text-gray-900 text-right">
|
<div className="text-xs lg:text-sm text-gray-900 text-right">
|
||||||
@ -787,8 +839,10 @@ export function InitiatorProposalApprovalModal({
|
|||||||
</div>
|
</div>
|
||||||
{/* Warning for IO not blocked - shown below Approve button */}
|
{/* Warning for IO not blocked - shown below Approve button */}
|
||||||
{!isIOBlocked && (
|
{!isIOBlocked && (
|
||||||
<p className="text-xs text-red-600 text-center sm:text-left">
|
<p className="text-xs text-red-600 text-center sm:text-left font-medium">
|
||||||
Please block IO budget in the IO Tab before approving
|
{totalBlockedAmount > 0
|
||||||
|
? `Pending block: ₹${remainingBaseToBlock.toLocaleString('en-IN', { minimumFractionDigits: 2 })} more needs to be blocked in the IO Tab.`
|
||||||
|
: "Please block IO budget in the IO Tab before approving."}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -155,8 +155,12 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
const currentUserId = (user as any)?.userId || '';
|
const currentUserId = (user as any)?.userId || '';
|
||||||
|
|
||||||
// IO tab visibility for dealer claims
|
// IO tab visibility for dealer claims
|
||||||
// Show IO tab for initiator (Requestor Evaluation level) - initiator can now fetch and block IO
|
// Restricted: Hide from Dealers, show for internal roles (Initiator, Dept Lead, Finance, Admin)
|
||||||
const showIOTab = isInitiator;
|
const isDealer = (user as any)?.jobTitle === 'Dealer' || (user as any)?.designation === 'Dealer';
|
||||||
|
const isClaimManagement = request?.workflowType === 'CLAIM_MANAGEMENT' ||
|
||||||
|
apiRequest?.workflowType === 'CLAIM_MANAGEMENT' ||
|
||||||
|
request?.templateType === 'claim-management';
|
||||||
|
const showIOTab = isClaimManagement && !isDealer;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mergedMessages,
|
mergedMessages,
|
||||||
|
|||||||
@ -238,6 +238,7 @@ export function useRequestDetails(
|
|||||||
let proposalDetails = null;
|
let proposalDetails = null;
|
||||||
let completionDetails = null;
|
let completionDetails = null;
|
||||||
let internalOrder = null;
|
let internalOrder = null;
|
||||||
|
let internalOrders = [];
|
||||||
|
|
||||||
if (wf.workflowType === 'CLAIM_MANAGEMENT' || wf.templateType === 'claim-management') {
|
if (wf.workflowType === 'CLAIM_MANAGEMENT' || wf.templateType === 'claim-management') {
|
||||||
try {
|
try {
|
||||||
@ -250,6 +251,7 @@ export function useRequestDetails(
|
|||||||
proposalDetails = claimData.proposalDetails || claimData.proposal_details;
|
proposalDetails = claimData.proposalDetails || claimData.proposal_details;
|
||||||
completionDetails = claimData.completionDetails || claimData.completion_details;
|
completionDetails = claimData.completionDetails || claimData.completion_details;
|
||||||
internalOrder = claimData.internalOrder || claimData.internal_order || null;
|
internalOrder = claimData.internalOrder || claimData.internal_order || null;
|
||||||
|
internalOrders = claimData.internalOrders || claimData.internal_orders || [];
|
||||||
// New normalized tables
|
// New normalized tables
|
||||||
const budgetTracking = claimData.budgetTracking || claimData.budget_tracking || null;
|
const budgetTracking = claimData.budgetTracking || claimData.budget_tracking || null;
|
||||||
const invoice = claimData.invoice || null;
|
const invoice = claimData.invoice || null;
|
||||||
@ -326,6 +328,7 @@ export function useRequestDetails(
|
|||||||
proposalDetails: proposalDetails || null,
|
proposalDetails: proposalDetails || null,
|
||||||
completionDetails: completionDetails || null,
|
completionDetails: completionDetails || null,
|
||||||
internalOrder: internalOrder || null,
|
internalOrder: internalOrder || null,
|
||||||
|
internalOrders: internalOrders || [],
|
||||||
// New normalized tables (also available via claimDetails for backward compatibility)
|
// New normalized tables (also available via claimDetails for backward compatibility)
|
||||||
budgetTracking: (claimDetails as any)?.budgetTracking || null,
|
budgetTracking: (claimDetails as any)?.budgetTracking || null,
|
||||||
invoice: (claimDetails as any)?.invoice || null,
|
invoice: (claimDetails as any)?.invoice || null,
|
||||||
@ -517,6 +520,7 @@ export function useRequestDetails(
|
|||||||
let proposalDetails = null;
|
let proposalDetails = null;
|
||||||
let completionDetails = null;
|
let completionDetails = null;
|
||||||
let internalOrder = null;
|
let internalOrder = null;
|
||||||
|
let internalOrders = [];
|
||||||
|
|
||||||
if (wf.workflowType === 'CLAIM_MANAGEMENT' || wf.templateType === 'claim-management') {
|
if (wf.workflowType === 'CLAIM_MANAGEMENT' || wf.templateType === 'claim-management') {
|
||||||
try {
|
try {
|
||||||
@ -528,6 +532,7 @@ export function useRequestDetails(
|
|||||||
proposalDetails = claimData.proposalDetails || claimData.proposal_details;
|
proposalDetails = claimData.proposalDetails || claimData.proposal_details;
|
||||||
completionDetails = claimData.completionDetails || claimData.completion_details;
|
completionDetails = claimData.completionDetails || claimData.completion_details;
|
||||||
internalOrder = claimData.internalOrder || claimData.internal_order || null;
|
internalOrder = claimData.internalOrder || claimData.internal_order || null;
|
||||||
|
internalOrders = claimData.internalOrders || claimData.internal_orders || [];
|
||||||
// New normalized tables
|
// New normalized tables
|
||||||
const budgetTracking = claimData.budgetTracking || claimData.budget_tracking || null;
|
const budgetTracking = claimData.budgetTracking || claimData.budget_tracking || null;
|
||||||
const invoice = claimData.invoice || null;
|
const invoice = claimData.invoice || null;
|
||||||
@ -591,6 +596,7 @@ export function useRequestDetails(
|
|||||||
proposalDetails: proposalDetails || null,
|
proposalDetails: proposalDetails || null,
|
||||||
completionDetails: completionDetails || null,
|
completionDetails: completionDetails || null,
|
||||||
internalOrder: internalOrder || null,
|
internalOrder: internalOrder || null,
|
||||||
|
internalOrders: internalOrders || [],
|
||||||
// New normalized tables (also available via claimDetails for backward compatibility)
|
// New normalized tables (also available via claimDetails for backward compatibility)
|
||||||
budgetTracking: (claimDetails as any)?.budgetTracking || null,
|
budgetTracking: (claimDetails as any)?.budgetTracking || null,
|
||||||
invoice: (claimDetails as any)?.invoice || null,
|
invoice: (claimDetails as any)?.invoice || null,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user