@@ -1211,7 +1232,7 @@ export function ClaimApproverSelectionStep({
⚠️ Additional approvers cannot be added after "Requestor Claim Approval" as it is considered final.
-
+
{/* Max Approval Levels Note */}
{maxApprovalLevels && (
@@ -1290,7 +1311,7 @@ export function ClaimApproverSelectionStep({
className="pl-10 h-11 border-gray-300"
autoFocus
/>
-
+
{/* Search Results Dropdown */}
{(isSearchingApprover || addApproverSearchResults.length > 0) && (
diff --git a/src/dealer-claim/components/request-detail/WorkflowTab.tsx b/src/dealer-claim/components/request-detail/WorkflowTab.tsx
index a714a9f..068204d 100644
--- a/src/dealer-claim/components/request-detail/WorkflowTab.tsx
+++ b/src/dealer-claim/components/request-detail/WorkflowTab.tsx
@@ -29,6 +29,7 @@ import { toast } from 'sonner';
import { submitProposal, updateIODetails, submitCompletion, updateEInvoice, sendCreditNoteToDealer } from '@/services/dealerClaimApi';
import { getWorkflowDetails, approveLevel, rejectLevel, handleInitiatorAction, getWorkflowHistory } from '@/services/workflowApi';
import { uploadDocument } from '@/services/documentApi';
+import { TokenManager } from '@/utils/tokenManager';
interface DealerClaimWorkflowTabProps {
request: any;
@@ -69,6 +70,7 @@ interface WorkflowStep {
};
einvoiceUrl?: string;
emailTemplateUrl?: string;
+ levelName?: string;
versionHistory?: {
current: any;
previous: any;
@@ -329,28 +331,50 @@ export function DealerClaimWorkflowTab({
// Step title and description mapping based on actual step number (not array index)
// This handles cases where approvers are added between steps
const getStepTitle = (stepNumber: number, levelName?: string, approverName?: string): string => {
+ // Check if this is a legacy workflow (8 steps) or new workflow (5 steps)
+ // Legacy flows have system steps (Activity, E-Invoice, Credit Note) as approval levels
+ const isLegacyFlow = (request?.totalLevels || 0) > 5 || (request?.approvalLevels?.length || 0) > 5;
+
// Use levelName from backend if available (most accurate)
// Check if it's an "Additional Approver" - this indicates a dynamically added approver
if (levelName && levelName.trim()) {
+ const levelNameLower = levelName.toLowerCase();
+
// If it starts with "Additional Approver", use it as-is (it's already formatted)
- if (levelName.toLowerCase().includes('additional approver')) {
+ if (levelNameLower.includes('additional approver')) {
+ return levelName;
+ }
+
+ // If levelName is NOT generic "Step X", return it
+ // This fixes the issue where backend sends "Step 1" instead of "Dealer Proposal Submission"
+ if (!/^step\s+\d+$/i.test(levelName)) {
return levelName;
}
- // Otherwise use the levelName from backend (preserved from original step)
- return levelName;
}
- // Fallback to mapping based on step number
- const stepTitleMap: Record = {
- 1: 'Dealer - Proposal Submission',
- 2: 'Requestor Evaluation & Confirmation',
- 3: 'Department Lead Approval',
- 4: 'Activity Creation',
- 5: 'Dealer - Completion Documents',
- 6: 'Requestor - Claim Approval',
- 7: 'E-Invoice Generation',
- 8: 'Credit Note from SAP',
- };
+ // Fallback to mapping based on step number and flow version
+ const stepTitleMap: Record = isLegacyFlow
+ ? {
+ // Legacy 8-step flow
+ 1: 'Dealer - Proposal Submission',
+ 2: 'Requestor Evaluation & Confirmation',
+ 3: 'Department Lead Approval',
+ 4: 'Activity Creation',
+ 5: 'Dealer - Completion Documents',
+ 6: 'Requestor - Claim Approval',
+ 7: 'E-Invoice Generation',
+ 8: 'Credit Note from SAP',
+ }
+ : {
+ // New 5-step flow
+ 1: 'Dealer - Proposal Submission',
+ 2: 'Requestor Evaluation & Confirmation',
+ 3: 'Department Lead Approval',
+ 4: 'Dealer - Completion Documents',
+ 5: 'Requestor - Claim Approval',
+ 6: 'E-Invoice Generation',
+ 7: 'Credit Note from SAP',
+ };
// If step number exists in map, use it
if (stepTitleMap[stepNumber]) {
@@ -377,6 +401,9 @@ export function DealerClaimWorkflowTab({
return `Additional approver will review and approve this request.`;
}
+ // Check if this is a legacy workflow (8 steps) or new workflow (5 steps)
+ const isLegacyFlow = (request?.totalLevels || 0) > 5 || (request?.approvalLevels?.length || 0) > 5;
+
// Use levelName to determine description (handles shifted steps correctly)
// This ensures descriptions shift with their steps when approvers are added
if (levelName && levelName.trim()) {
@@ -392,6 +419,7 @@ export function DealerClaimWorkflowTab({
if (levelNameLower.includes('department lead')) {
return 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)';
}
+ // Re-added for legacy support
if (levelNameLower.includes('activity creation')) {
return 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.';
}
@@ -401,25 +429,37 @@ export function DealerClaimWorkflowTab({
if (levelNameLower.includes('requestor') && (levelNameLower.includes('claim') || levelNameLower.includes('approval'))) {
return 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.';
}
- if (levelNameLower.includes('e-invoice') || levelNameLower.includes('invoice generation')) {
- return 'E-invoice will be generated through DMS.';
+ if (levelNameLower.includes('e-invoice') || levelNameLower.includes('invoice generation') || levelNameLower.includes('dms')) {
+ return 'E-Invoice will be generated upon settlement initiation.';
}
if (levelNameLower.includes('credit note') || levelNameLower.includes('sap')) {
return 'Got credit note from SAP. Review and send to dealer to complete the claim management process.';
}
}
- // Fallback to step number mapping (for backwards compatibility)
- const stepDescriptionMap: Record = {
- 1: 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests',
- 2: 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)',
- 3: 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)',
- 4: 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.',
- 5: 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description',
- 6: 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.',
- 7: 'E-invoice will be generated through DMS.',
- 8: 'Got credit note from SAP. Review and send to dealer to complete the claim management process.',
- };
+ // Fallback to step number mapping depending on flow version
+ const stepDescriptionMap: Record = isLegacyFlow
+ ? {
+ // Legacy 8-step flow
+ 1: 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests',
+ 2: 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)',
+ 3: 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)',
+ 4: 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.',
+ 5: 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description',
+ 6: 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.',
+ 7: 'E-Invoice will be generated upon settlement initiation.',
+ 8: 'Got credit note from SAP. Review and send to dealer to complete the claim management process.',
+ }
+ : {
+ // New 5-step flow
+ 1: 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests',
+ 2: 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)',
+ 3: 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)',
+ 4: 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description',
+ 5: 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.',
+ 6: 'E-Invoice will be generated upon settlement initiation.',
+ 7: 'Got credit note from SAP. Review and send to dealer to complete the claim management process.',
+ };
if (stepDescriptionMap[stepNumber]) {
return stepDescriptionMap[stepNumber];
@@ -845,13 +885,26 @@ export function DealerClaimWorkflowTab({
await uploadDocument(file, requestId, 'SUPPORTING');
}
- // Submit proposal using dealer claim API
- const totalBudget = data.costBreakup.reduce((sum, item) => sum + item.amount, 0);
+ // Submit proposal using dealer claim API (calculate total from inclusive item totals)
+ const totalBudget = data.costBreakup.reduce((sum, item: any) => sum + (item.totalAmt || item.amount || 0), 0);
await submitProposal(requestId, {
proposalDocument: data.proposalDocument || undefined,
- costBreakup: data.costBreakup.map(item => ({
+ costBreakup: data.costBreakup.map((item: any) => ({
description: item.description,
amount: item.amount,
+ gstRate: item.gstRate,
+ gstAmt: item.gstAmt,
+ cgstRate: item.cgstRate,
+ cgstAmt: item.cgstAmt,
+ sgstRate: item.sgstRate,
+ sgstAmt: item.sgstAmt,
+ igstRate: item.igstRate,
+ igstAmt: item.igstAmt,
+ utgstRate: item.utgstRate,
+ utgstAmt: item.utgstAmt,
+ cessRate: item.cessRate,
+ cessAmt: item.cessAmt,
+ totalAmt: item.totalAmt
})),
totalEstimatedBudget: totalBudget,
expectedCompletionDate: data.expectedCompletionDate,
@@ -1106,10 +1159,23 @@ export function DealerClaimWorkflowTab({
const requestId = request.id || request.requestId;
- // Transform expense items to match API format
- const closedExpenses = data.closedExpenses.map(item => ({
+ // Transform expense items to match API format (include GST fields)
+ const closedExpenses = data.closedExpenses.map((item: any) => ({
description: item.description,
amount: item.amount,
+ gstRate: item.gstRate,
+ gstAmt: item.gstAmt,
+ cgstRate: item.cgstRate,
+ cgstAmt: item.cgstAmt,
+ sgstRate: item.sgstRate,
+ sgstAmt: item.sgstAmt,
+ igstRate: item.igstRate,
+ igstAmt: item.igstAmt,
+ utgstRate: item.utgstRate,
+ utgstAmt: item.utgstAmt,
+ cessRate: item.cessRate,
+ cessAmt: item.cessAmt,
+ totalAmt: item.totalAmt
}));
// Submit completion documents using dealer claim API
@@ -1145,7 +1211,7 @@ export function DealerClaimWorkflowTab({
}
};
- // Handle DMS push (Step 6)
+ // Handle E-Invoice generation (Step 6)
const handleDMSPush = async (_comments: string) => {
try {
if (!request?.id && !request?.requestId) {
@@ -1162,11 +1228,11 @@ export function DealerClaimWorkflowTab({
});
// Activity is logged by backend service - no need to create work note
- toast.success('Pushed to DMS successfully. E-invoice will be generated automatically.');
+ toast.success('E-Invoice generation initiated successfully.');
handleRefresh();
} catch (error: any) {
- console.error('[DealerClaimWorkflowTab] Error pushing to DMS:', error);
- const errorMessage = error?.response?.data?.message || error?.message || 'Failed to push to DMS. Please try again.';
+ console.error('[DealerClaimWorkflowTab] Error generating e-invoice:', error);
+ const errorMessage = error?.response?.data?.message || error?.message || 'Failed to generate e-invoice. Please try again.';
toast.error(errorMessage);
throw error;
}
@@ -1381,6 +1447,31 @@ export function DealerClaimWorkflowTab({
loadCompletionDocuments();
}, [request]);
+ const handlePreviewInvoice = async () => {
+ try {
+ const requestId = request.id || request.requestId;
+ if (!requestId) {
+ toast.error('Request ID not found');
+ return;
+ }
+
+ // Check if invoice exists
+ if (!request.invoice && !request.irn) {
+ toast.error('Invoice not generated yet');
+ return;
+ }
+
+ const token = TokenManager.getAccessToken();
+ // Construct API URL for PDF preview
+ const previewUrl = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api/v1'}/dealer-claims/${requestId}/e-invoice/pdf?token=${token}`;
+
+ window.open(previewUrl, '_blank');
+ } catch (error) {
+ console.error('Failed to preview invoice:', error);
+ toast.error('Failed to open invoice preview');
+ }
+ };
+
// Get dealer and activity info
const dealerName = request?.claimDetails?.dealerName ||
request?.dealerInfo?.name ||
@@ -1513,6 +1604,23 @@ export function DealerClaimWorkflowTab({
)}
+ {/* Invoice Preview Button (Requestor Claim Approval) */}
+ {(() => {
+ const isRequestorClaimStep = (step.levelName || step.title || '').toLowerCase().includes('requestor claim') ||
+ (step.levelName || step.title || '').toLowerCase().includes('requestor - claim');
+ const hasInvoice = request?.invoice || (request?.irn && step.status === 'approved');
+ return isRequestorClaimStep && hasInvoice && (
+
+
+
+ );
+ })()}
{step.approver}
{step.description}
@@ -1721,10 +1829,10 @@ export function DealerClaimWorkflowTab({
{/* Current Approver - Time Tracking */}
= 100 ? 'bg-red-50 border-red-200' :
- (approval.sla.percentageUsed || 0) >= 75 ? 'bg-orange-50 border-orange-200' :
- (approval.sla.percentageUsed || 0) >= 50 ? 'bg-amber-50 border-amber-200' :
- 'bg-green-50 border-green-200'
+ (approval.sla.percentageUsed || 0) >= 100 ? 'bg-red-50 border-red-200' :
+ (approval.sla.percentageUsed || 0) >= 75 ? 'bg-orange-50 border-orange-200' :
+ (approval.sla.percentageUsed || 0) >= 50 ? 'bg-amber-50 border-amber-200' :
+ 'bg-green-50 border-green-200'
}`}>
@@ -1856,25 +1964,25 @@ export function DealerClaimWorkflowTab({
- DMS Processing Details
+ E-Invoice & Settlement Details
- DMS Number:
+ Settlement ID:
{step.dmsDetails.dmsNumber}
{step.dmsDetails.dmsRemarks && (
-
DMS Remarks:
+
Settlement Remarks:
{step.dmsDetails.dmsRemarks}
)}
{step.dmsDetails.pushedAt && (
- Pushed by {step.dmsDetails.pushedBy} on{' '}
+ Initiated by {step.dmsDetails.pushedBy} on{' '}
{formatDateSafe(step.dmsDetails.pushedAt)}
)}
@@ -1902,33 +2010,51 @@ export function DealerClaimWorkflowTab({
})() && (
{/* Step 1: Submit Proposal Button - Only for dealer or step 1 approver */}
- {step.step === 1 && (isDealer || isStep1Approver) && (
-
{
- setShowProposalModal(true);
- }}
- >
-
- Submit Proposal
-
- )}
+ {(() => {
+ // Check if this is Step 1 (Dealer Proposal Submission)
+ // Use levelName match or fallback to step 1
+ const levelName = (stepLevel?.levelName || step.title || '').toLowerCase();
+ const isProposalStep = step.step === 1 ||
+ levelName.includes('proposal') ||
+ levelName.includes('submission');
+
+ return isProposalStep && (isDealer || isStep1Approver);
+ })() && (
+
{
+ setShowProposalModal(true);
+ }}
+ >
+
+ Submit Proposal
+
+ )}
{/* Step 2 (or shifted step): Review Request - Only for initiator or step approver */}
{/* Use initiatorStepNumber to handle cases where approvers are added between steps */}
{/* Step 2 (or shifted step): Review Request - Only for initiator or step approver */}
{/* Use initiatorStepNumber to handle cases where approvers are added between steps */}
- {step.step === initiatorStepNumber && (isInitiator || isStep2Approver) && (
-
{
- setShowApprovalModal(true);
- }}
- >
-
- Review Request
-
- )}
+ {/* Step 2 (or shifted step): Review Request - Only for initiator or step approver */}
+ {(() => {
+ // Check if this is the Requestor Evaluation step
+ const levelName = (stepLevel?.levelName || step.title || '').toLowerCase();
+ const isEvaluationStep = levelName.includes('requestor evaluation') ||
+ levelName.includes('confirmation') ||
+ step.step === initiatorStepNumber; // Fallback
+
+ return isEvaluationStep && (isInitiator || isStep2Approver);
+ })() && (
+
{
+ setShowApprovalModal(true);
+ }}
+ >
+
+ Review Request
+
+ )}
{/* Initiator Action Step: Show action buttons (REVISE, REOPEN) - Direct actions, no modal */}
{(() => {
@@ -2080,20 +2206,26 @@ export function DealerClaimWorkflowTab({
}}
>
- Push to DMS
+ Generate E-Invoice & Sync
);
})()}
{/* Step 8: View & Send Credit Note - Only for finance approver or step 8 approver */}
- {step.step === 8 && (() => {
- const step8Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 8);
- const step8ApproverEmail = (step8Level?.approverEmail || '').toLowerCase();
- const isStep8Approver = step8ApproverEmail && userEmail === step8ApproverEmail;
+ {(() => {
+ const levelName = (stepLevel?.levelName || step.title || '').toLowerCase();
+ // Check for "Credit Note" or "SAP" in level name, or fallback to step 8 if it's the last step
+ const isCreditNoteStep = levelName.includes('credit note') ||
+ levelName.includes('sap') ||
+ (step.step === 8 && !levelName.includes('additional'));
+
+ const stepApproverEmail = (stepLevel?.approverEmail || '').toLowerCase();
+ const isStepApprover = stepApproverEmail && userEmail === stepApproverEmail;
// Also check if user has finance role
const userRole = (user as any)?.role?.toUpperCase() || '';
const isFinanceUser = userRole === 'FINANCE' || userRole === 'ADMIN';
- return isStep8Approver || isFinanceUser;
+
+ return isCreditNoteStep && (isStepApprover || isFinanceUser);
})() && (
{
- const deptLeadStepLevel = approvalFlow.find((l: any) => {
- const ln = (l.levelName || '').toLowerCase();
- return ln.includes('department lead');
- });
- return deptLeadStepLevel &&
- (step.step === (deptLeadStepLevel.step || deptLeadStepLevel.levelNumber || deptLeadStepLevel.level_number));
- })() ||
- (() => {
- const stepLevel = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === step.step);
- const stepApproverEmail = (stepLevel?.approverEmail || '').toLowerCase();
- const isDealerForThisStep = isDealer && stepApproverEmail === dealerEmail;
- const ln = (stepLevel?.levelName || step.title || '').toLowerCase();
- const isDealerCompletionStep = ln.includes('dealer completion') || ln.includes('completion documents');
- return isDealerForThisStep && isDealerCompletionStep;
- })() ||
- (() => {
- const requestorClaimStepLevel = approvalFlow.find((l: any) => {
- const ln = (l.levelName || '').toLowerCase();
- return ln.includes('requestor claim') || ln.includes('requestor - claim');
- });
- return requestorClaimStepLevel &&
- (step.step === (requestorClaimStepLevel.step || requestorClaimStepLevel.levelNumber || requestorClaimStepLevel.level_number));
- })() ||
- step.step === 8;
+ // Proposal
+ (step.step === 1 || levelName.includes('proposal') || levelName.includes('submission')) ||
+ // Evaluation
+ (levelName.includes('requestor evaluation') || levelName.includes('confirmation')) ||
+ // Dept Lead
+ levelName.includes('department lead') ||
+ // Dealer Completion
+ (levelName.includes('dealer completion') || levelName.includes('completion documents')) ||
+ // Requestor Claim
+ (levelName.includes('requestor claim') || levelName.includes('requestor - claim')) ||
+ // Credit Note
+ (levelName.includes('credit note') || levelName.includes('sap'));
// Show "Review Request" button for additional approvers or steps without specific workflow actions
// Similar to the requestor approval step
diff --git a/src/dealer-claim/components/request-detail/claim-cards/ProcessDetailsCard.tsx b/src/dealer-claim/components/request-detail/claim-cards/ProcessDetailsCard.tsx
index a2b6d39..354f2a0 100644
--- a/src/dealer-claim/components/request-detail/claim-cards/ProcessDetailsCard.tsx
+++ b/src/dealer-claim/components/request-detail/claim-cards/ProcessDetailsCard.tsx
@@ -1,6 +1,6 @@
/**
* ProcessDetailsCard Component
- * Displays process-related details: IO Number, DMS Number, Claim Amount, and Budget Breakdowns
+ * Displays process-related details: IO Number, E-Invoice, Claim Amount, and Budget Breakdowns
* Visibility controlled by user role
*/
@@ -172,21 +172,18 @@ export function ProcessDetailsCard({
)}
- {/* DMS Details */}
+ {/* E-Invoice Details */}
{visibility.showDMSDetails && dmsDetails && (
- DMS & E-Invoice Details
+ E-Invoice Details
-
-
DMS Number
-
{dmsDetails.dmsNumber || 'N/A'}
-
+
{dmsDetails.ackNo && (
Ack No
diff --git a/src/dealer-claim/components/request-detail/claim-cards/ProposalDetailsCard.tsx b/src/dealer-claim/components/request-detail/claim-cards/ProposalDetailsCard.tsx
index 21b0086..438b313 100644
--- a/src/dealer-claim/components/request-detail/claim-cards/ProposalDetailsCard.tsx
+++ b/src/dealer-claim/components/request-detail/claim-cards/ProposalDetailsCard.tsx
@@ -22,6 +22,7 @@ interface ProposalCostItem {
interface ProposalDetails {
costBreakup: ProposalCostItem[];
estimatedBudgetTotal?: number | null;
+ totalEstimatedBudget?: number | null;
timelineForClosure?: string | null;
dealerComments?: string | null;
submittedOn?: string | null;
@@ -35,8 +36,9 @@ interface ProposalDetailsCardProps {
export function ProposalDetailsCard({ proposalDetails, className }: ProposalDetailsCardProps) {
// Calculate estimated total from costBreakup if not provided
const calculateEstimatedTotal = () => {
- if (proposalDetails.estimatedBudgetTotal !== undefined && proposalDetails.estimatedBudgetTotal !== null) {
- return proposalDetails.estimatedBudgetTotal;
+ const total = proposalDetails.totalEstimatedBudget ?? proposalDetails.estimatedBudgetTotal;
+ if (total !== undefined && total !== null) {
+ return total;
}
// Calculate sum from costBreakup items
diff --git a/src/dealer-claim/components/request-detail/modals/DMSPushModal.css b/src/dealer-claim/components/request-detail/modals/DMSPushModal.css
index caeba56..e22f510 100644
--- a/src/dealer-claim/components/request-detail/modals/DMSPushModal.css
+++ b/src/dealer-claim/components/request-detail/modals/DMSPushModal.css
@@ -1,12 +1,16 @@
-.dms-push-modal {
+.settlement-push-modal {
width: 90vw !important;
- max-width: 90vw !important;
+ max-width: 1000px !important;
+ min-width: 320px !important;
max-height: 95vh !important;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
}
/* Mobile responsive */
@media (max-width: 640px) {
- .dms-push-modal {
+ .settlement-push-modal {
width: 95vw !important;
max-width: 95vw !important;
max-height: 95vh !important;
@@ -15,25 +19,48 @@
/* Tablet and small desktop */
@media (min-width: 641px) and (max-width: 1023px) {
- .dms-push-modal {
+ .settlement-push-modal {
width: 90vw !important;
- max-width: 90vw !important;
+ max-width: 900px !important;
}
}
-/* Large screens - fixed max-width for better readability */
-@media (min-width: 1024px) {
- .dms-push-modal {
- width: 90vw !important;
- max-width: 1000px !important;
- }
+/* Scrollable content area */
+.settlement-push-modal .flex-1 {
+ overflow-y: auto;
+ padding-right: 4px;
}
-/* Extra large screens */
-@media (min-width: 1536px) {
- .dms-push-modal {
- width: 90vw !important;
- max-width: 1000px !important;
- }
+/* Custom scrollbar for the modal content */
+.settlement-push-modal .flex-1::-webkit-scrollbar {
+ width: 6px;
}
+.settlement-push-modal .flex-1::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.settlement-push-modal .flex-1::-webkit-scrollbar-thumb {
+ background: #e2e8f0;
+ border-radius: 10px;
+}
+
+.settlement-push-modal .flex-1::-webkit-scrollbar-thumb:hover {
+ background: #cbd5e1;
+}
+
+.file-preview-dialog {
+ width: 95vw !important;
+ max-width: 1200px !important;
+ max-height: 95vh !important;
+ padding: 0 !important;
+ display: flex;
+ flex-direction: column;
+}
+
+.file-preview-content {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/dealer-claim/components/request-detail/modals/DMSPushModal.tsx b/src/dealer-claim/components/request-detail/modals/DMSPushModal.tsx
index 1d44313..944cd83 100644
--- a/src/dealer-claim/components/request-detail/modals/DMSPushModal.tsx
+++ b/src/dealer-claim/components/request-detail/modals/DMSPushModal.tsx
@@ -149,11 +149,11 @@ export function DMSPushModal({
if (!doc.name) return false;
const name = doc.name.toLowerCase();
return name.endsWith('.pdf') ||
- name.endsWith('.jpg') ||
- name.endsWith('.jpeg') ||
- name.endsWith('.png') ||
- name.endsWith('.gif') ||
- name.endsWith('.webp');
+ name.endsWith('.jpg') ||
+ name.endsWith('.jpeg') ||
+ name.endsWith('.png') ||
+ name.endsWith('.gif') ||
+ name.endsWith('.webp');
};
// Handle document preview - fetch as blob to avoid CSP issues
@@ -228,7 +228,7 @@ export function DMSPushModal({
const handleSubmit = async () => {
if (!comments.trim()) {
- toast.error('Please provide comments before pushing to DMS');
+ toast.error('Please provide comments before proceeding');
return;
}
@@ -238,8 +238,8 @@ export function DMSPushModal({
handleReset();
onClose();
} catch (error) {
- console.error('Failed to push to DMS:', error);
- toast.error('Failed to push to DMS. Please try again.');
+ console.error('Failed to generate e-invoice:', error);
+ toast.error('Failed to generate e-invoice. Please try again.');
} finally {
setSubmitting(false);
}
@@ -257,511 +257,514 @@ export function DMSPushModal({
};
return (
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+ E-Invoice Generation & Sync
+
+
+ Review completion details and expenses before generating e-invoice and initiating SAP settlement
+
+
-
-
- Push to DMS - Verification
-
-
- Review completion details and expenses before pushing to DMS for e-invoice generation
-
-
-
- {/* Request Info Card - Grid layout */}
-
-
- Workflow Step:
- Requestor Claim Approval
-
- {requestNumber && (
+ {/* Request Info Card - Grid layout */}
+
+
+ Workflow Step:
+ Requestor Claim Approval
+
+ {requestNumber && (
+
+
Request Number:
+
{requestNumber}
+
+ )}
-
Request Number:
-
{requestNumber}
+
Title:
+
{requestTitle || '—'}
+
+
+
Action:
+
+
+ SYNC TO SAP
+
- )}
-
-
Title:
-
{requestTitle || '—'}
-
-
Action:
-
-
- PUSH TO DMS
-
-
-
-
+
-
-
- {/* Grid layout for all three cards on larger screens */}
-
- {/* Completion Details Card */}
- {completionDetails && (
-
-
-
-
- Completion Details
-
-
- Review activity completion information
-
-
-
- {completionDetails.activityCompletionDate && (
-
- Activity Completion Date:
-
- {formatDate(completionDetails.activityCompletionDate)}
-
-
- )}
- {completionDetails.numberOfParticipants !== undefined && (
-
- Number of Participants:
-
- {completionDetails.numberOfParticipants}
-
-
- )}
- {completionDetails.completionDescription && (
-
-
Completion Description:
-
- {completionDetails.completionDescription}
-
-
- )}
-
-
- )}
-
- {/* IO Details Card */}
- {ioDetails && ioDetails.ioNumber && (
-
-
-
-
- IO Details
-
-
- Internal Order information for budget reference
-
-
-
-
- IO Number:
-
- {ioDetails.ioNumber}
-
-
- {ioDetails.blockedAmount !== undefined && ioDetails.blockedAmount > 0 && (
-
- Blocked Amount:
-
- {formatCurrency(ioDetails.blockedAmount)}
-
-
- )}
- {ioDetails.remainingBalance !== undefined && ioDetails.remainingBalance !== null && (
-
- Remaining Balance:
-
- {formatCurrency(ioDetails.remainingBalance)}
-
-
- )}
-
-
- )}
-
- {/* Expense Breakdown Card */}
- {completionDetails?.closedExpenses && completionDetails.closedExpenses.length > 0 && (
-
-
-
-
- Expense Breakdown
-
-
- Review closed expenses before pushing to DMS
-
-
-
-
- {completionDetails.closedExpenses.map((expense, index) => (
-
-
-
- {expense.description || `Expense ${index + 1}`}
+
+
+ {/* Grid layout for all three cards on larger screens */}
+
+ {/* Completion Details Card */}
+ {completionDetails && (
+
+
+
+
+ Completion Details
+
+
+ Review activity completion information
+
+
+
+ {completionDetails.activityCompletionDate && (
+
+ Activity Completion Date:
+
+ {formatDate(completionDetails.activityCompletionDate)}
+
+
+ )}
+ {completionDetails.numberOfParticipants !== undefined && (
+
+ Number of Participants:
+
+ {completionDetails.numberOfParticipants}
+
+
+ )}
+ {completionDetails.completionDescription && (
+
+
Completion Description:
+
+ {completionDetails.completionDescription}
-
-
- {formatCurrency(typeof expense === 'object' ? (expense.amount || 0) : 0)}
-
-
-
- ))}
-
-
- Total:
-
- {formatCurrency(totalClosedExpenses)}
-
-
-
-
- )}
-
+ )}
+
+
+ )}
- {/* Completion Documents Section */}
- {completionDocuments && (
-
- {/* Completion Documents */}
- {completionDocuments.completionDocuments && completionDocuments.completionDocuments.length > 0 && (
-
-
-
-
- Completion Documents
-
-
- {completionDocuments.completionDocuments.length} file(s)
-
-
-
- {completionDocuments.completionDocuments.map((doc, index) => (
-
-
-
-
+ {/* IO Details Card */}
+ {ioDetails && ioDetails.ioNumber && (
+
+
+
+
+ IO Details
+
+
+ Internal Order information for budget reference
+
+
+
+
+ IO Number:
+
+ {ioDetails.ioNumber}
+
+
+ {ioDetails.blockedAmount !== undefined && ioDetails.blockedAmount > 0 && (
+
+ Blocked Amount:
+
+ {formatCurrency(ioDetails.blockedAmount)}
+
- {doc.id && (
-
- {canPreviewDocument(doc) && (
- handlePreviewDocument(doc)}
- disabled={previewLoading}
- className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- title="Preview document"
- >
- {previewLoading ? (
-
- ) : (
-
- )}
-
- )}
- {
- try {
- if (doc.id) {
- await downloadDocument(doc.id);
- }
- } catch (error) {
- console.error('Failed to download document:', error);
- toast.error('Failed to download document');
- }
- }}
- className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
- title="Download document"
- >
-
-
-
- )}
-
- ))}
-
-
- )}
-
- {/* Activity Photos */}
- {completionDocuments.activityPhotos && completionDocuments.activityPhotos.length > 0 && (
-
-
-
-
- Activity Photos
-
-
- {completionDocuments.activityPhotos.length} file(s)
-
-
-
- {completionDocuments.activityPhotos.map((doc, index) => (
-
-
-
-
+ )}
+ {ioDetails.remainingBalance !== undefined && ioDetails.remainingBalance !== null && (
+
+ Remaining Balance:
+
+ {formatCurrency(ioDetails.remainingBalance)}
+
- {doc.id && (
-
- {canPreviewDocument(doc) && (
- handlePreviewDocument(doc)}
- disabled={previewLoading}
- className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- title="Preview photo"
- >
- {previewLoading ? (
-
- ) : (
-
- )}
-
- )}
- {
- try {
- if (doc.id) {
- await downloadDocument(doc.id);
- }
- } catch (error) {
- console.error('Failed to download document:', error);
- toast.error('Failed to download document');
- }
- }}
- className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
- title="Download photo"
- >
-
-
-
- )}
-
- ))}
-
-
- )}
+ )}
+
+
+ )}
- {/* Invoices / Receipts */}
- {completionDocuments.invoicesReceipts && completionDocuments.invoicesReceipts.length > 0 && (
-
-
-
-
- Invoices / Receipts
-
-
- {completionDocuments.invoicesReceipts.length} file(s)
-
-
-
- {completionDocuments.invoicesReceipts.map((doc, index) => (
-
-
- {doc.id && (
-
- {canPreviewDocument(doc) && (
- handlePreviewDocument(doc)}
- disabled={previewLoading}
- className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- title="Preview document"
- >
- {previewLoading ? (
-
- ) : (
-
- )}
-
- )}
- {
- try {
- if (doc.id) {
- await downloadDocument(doc.id);
- }
- } catch (error) {
- console.error('Failed to download document:', error);
- toast.error('Failed to download document');
- }
- }}
- className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
- title="Download document"
- >
-
-
-
- )}
-
- ))}
-
-
- )}
-
- {/* Attendance Sheet */}
- {completionDocuments.attendanceSheet && (
-
-
-
-
- Attendance Sheet
-
-
-
-
-
-
-
- {completionDocuments.attendanceSheet.name}
-
-
-
- {completionDocuments.attendanceSheet.id && (
-
- {canPreviewDocument(completionDocuments.attendanceSheet) && (
-
handlePreviewDocument(completionDocuments.attendanceSheet!)}
- disabled={previewLoading}
- className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- title="Preview document"
+ {/* Expense Breakdown Card */}
+ {completionDetails?.closedExpenses && completionDetails.closedExpenses.length > 0 && (
+
+
+
+
+ Expense Breakdown
+
+
+ Review closed expenses before generation
+
+
+
+
+ {completionDetails.closedExpenses.map((expense, index) => (
+
- {previewLoading ? (
-
- ) : (
-
- )}
-
- )}
-
{
- try {
- if (completionDocuments.attendanceSheet?.id) {
- await downloadDocument(completionDocuments.attendanceSheet.id);
- }
- } catch (error) {
- console.error('Failed to download document:', error);
- toast.error('Failed to download document');
- }
- }}
- className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
- title="Download document"
- >
-
-
+
+
+ {expense.description || `Expense ${index + 1}`}
+
+
+
+
+ {formatCurrency(typeof expense === 'object' ? (expense.amount || 0) : 0)}
+
+
+
+ ))}
- )}
-
+
+ Total:
+
+ {formatCurrency(totalClosedExpenses)}
+
+
+
+
+ )}
+
+
+ {/* Completion Documents Section */}
+ {completionDocuments && (
+
+ {/* Completion Documents */}
+ {completionDocuments.completionDocuments && completionDocuments.completionDocuments.length > 0 && (
+
+
+
+
+ Completion Documents
+
+
+ {completionDocuments.completionDocuments.length} file(s)
+
+
+
+ {completionDocuments.completionDocuments.map((doc, index) => (
+
+
+ {doc.id && (
+
+ {canPreviewDocument(doc) && (
+ handlePreviewDocument(doc)}
+ disabled={previewLoading}
+ className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+ title="Preview document"
+ >
+ {previewLoading ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {
+ try {
+ if (doc.id) {
+ await downloadDocument(doc.id);
+ }
+ } catch (error) {
+ console.error('Failed to download document:', error);
+ toast.error('Failed to download document');
+ }
+ }}
+ className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
+ title="Download document"
+ >
+
+
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {/* Activity Photos */}
+ {completionDocuments.activityPhotos && completionDocuments.activityPhotos.length > 0 && (
+
+
+
+
+ Activity Photos
+
+
+ {completionDocuments.activityPhotos.length} file(s)
+
+
+
+ {completionDocuments.activityPhotos.map((doc, index) => (
+
+
+ {doc.id && (
+
+ {canPreviewDocument(doc) && (
+ handlePreviewDocument(doc)}
+ disabled={previewLoading}
+ className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+ title="Preview photo"
+ >
+ {previewLoading ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {
+ try {
+ if (doc.id) {
+ await downloadDocument(doc.id);
+ }
+ } catch (error) {
+ console.error('Failed to download document:', error);
+ toast.error('Failed to download document');
+ }
+ }}
+ className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
+ title="Download photo"
+ >
+
+
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {/* Invoices / Receipts */}
+ {completionDocuments.invoicesReceipts && completionDocuments.invoicesReceipts.length > 0 && (
+
+
+
+
+ Invoices / Receipts
+
+
+ {completionDocuments.invoicesReceipts.length} file(s)
+
+
+
+ {completionDocuments.invoicesReceipts.map((doc, index) => (
+
+
+ {doc.id && (
+
+ {canPreviewDocument(doc) && (
+ handlePreviewDocument(doc)}
+ disabled={previewLoading}
+ className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+ title="Preview document"
+ >
+ {previewLoading ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {
+ try {
+ if (doc.id) {
+ await downloadDocument(doc.id);
+ }
+ } catch (error) {
+ console.error('Failed to download document:', error);
+ toast.error('Failed to download document');
+ }
+ }}
+ className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
+ title="Download document"
+ >
+
+
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {/* Attendance Sheet */}
+ {completionDocuments.attendanceSheet && (
+
+
+
+
+ Attendance Sheet
+
+
+
+
+
+
+
+ {completionDocuments.attendanceSheet.name}
+
+
+
+ {completionDocuments.attendanceSheet.id && (
+
+ {canPreviewDocument(completionDocuments.attendanceSheet) && (
+ handlePreviewDocument(completionDocuments.attendanceSheet!)}
+ disabled={previewLoading}
+ className="p-2 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+ title="Preview document"
+ >
+ {previewLoading ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {
+ try {
+ if (completionDocuments.attendanceSheet?.id) {
+ await downloadDocument(completionDocuments.attendanceSheet.id);
+ }
+ } catch (error) {
+ console.error('Failed to download document:', error);
+ toast.error('Failed to download document');
+ }
+ }}
+ className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
+ title="Download document"
+ >
+
+
+
+ )}
+
+
+ )}
)}
-
- )}
- {/* Verification Warning */}
-
-
-
-
-
- Please verify all details before pushing to DMS
-
-
- Once pushed, the system will automatically generate an e-invoice and log it as an activity.
-
+ {/* Verification Warning */}
+
+
+
+
+
+ Please verify all details before generation
+
+
+ Once submitted, the system will generate an e-invoice and initiate the SAP settlement process.
+
+
+
+
+
+ {/* Comments & Remarks */}
+
+
+ Comments & Remarks *
+
+
- {/* Comments & Remarks */}
-
-
- Comments & Remarks *
-
-
-
-
+
+
+ Cancel
+
+
+ {submitting ? (
+ 'Processing...'
+ ) : (
+ <>
+
+ Generate & Sync
+ >
+ )}
+
+
+
-
-
- Cancel
-
-
- {submitting ? (
- 'Pushing to DMS...'
- ) : (
- <>
-
- Push to DMS
- >
- )}
-
-
-
+
{/* File Preview Modal - Matching DocumentsTab style */}
{previewDocument && (
@@ -866,7 +869,7 @@ export function DMSPushModal({
)}
-
+ >
);
}
diff --git a/src/dealer-claim/pages/RequestDetail.tsx b/src/dealer-claim/pages/RequestDetail.tsx
index 50025e0..c36477c 100644
--- a/src/dealer-claim/pages/RequestDetail.tsx
+++ b/src/dealer-claim/pages/RequestDetail.tsx
@@ -41,7 +41,7 @@ import { downloadDocument } from '@/services/workflowApi';
import { getPublicConfigurations, AdminConfiguration } from '@/services/adminApi';
import { PolicyViolationModal } from '@/components/modals/PolicyViolationModal';
import { getSocket, joinUserRoom } from '@/utils/socket';
-import { isClaimManagementRequest } from '@/utils/claimRequestUtils';
+
// Dealer Claim Components (import from index to get properly aliased exports)
import { DealerClaimOverviewTab, DealerClaimWorkflowTab, IOTab } from '../index';
@@ -153,7 +153,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
// Determine if user is department lead (find dynamically by levelName, not hardcoded step number)
const currentUserId = (user as any)?.userId || '';
-
+
// IO tab visibility for dealer claims
// Show IO tab for initiator (Requestor Evaluation level) - initiator can now fetch and block IO
const showIOTab = isInitiator;
@@ -177,7 +177,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
// State to temporarily store approval level for modal (used for additional approvers)
const [temporaryApprovalLevel, setTemporaryApprovalLevel] = useState
(null);
-
+
// Use temporary level if set, otherwise use currentApprovalLevel
const effectiveApprovalLevel = temporaryApprovalLevel || currentApprovalLevel;
@@ -220,7 +220,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
// Check both lowercase and uppercase status values
const requestStatus = (request?.status || apiRequest?.status || '').toLowerCase();
const needsClosure = (requestStatus === 'approved' || requestStatus === 'rejected') && isInitiator;
-
+
// Closure check completed
const {
conclusionRemark,
@@ -335,7 +335,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
try {
setLoadingSummary(true);
const summary = await getSummaryByRequestId(apiRequest.requestId);
-
+
if (summary?.summaryId) {
setSummaryId(summary.summaryId);
try {
@@ -376,9 +376,9 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
const notifRequestId = notif.requestId || notif.request_id;
const notifRequestNumber = notif.metadata?.requestNumber || notif.metadata?.request_number;
- if (notifRequestId !== apiRequest.requestId &&
- notifRequestNumber !== requestIdentifier &&
- notifRequestNumber !== apiRequest.requestNumber) return;
+ if (notifRequestId !== apiRequest.requestId &&
+ notifRequestNumber !== requestIdentifier &&
+ notifRequestNumber !== apiRequest.requestNumber) return;
// Check for credit note metadata
if (notif.metadata?.creditNoteNumber || notif.metadata?.credit_note_number) {
@@ -427,15 +427,15 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
{accessDenied.message}
-
window.history.back())}
className="flex items-center gap-2"
>
Go Back
-
window.location.href = '/dashboard'}
className="bg-blue-600 hover:bg-blue-700"
>
@@ -460,15 +460,15 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
The dealer claim request you're looking for doesn't exist or may have been deleted.
-
window.history.back())}
className="flex items-center gap-2"
>
Go Back
-
window.location.href = '/dashboard'}
className="bg-blue-600 hover:bg-blue-700"
>
@@ -598,8 +598,8 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
{isClosed && (
- setShowAddApproverModal(true)}
onAddSpectator={() => setShowAddSpectatorModal(true)}
onApprove={() => setShowApproveModal(true)}
diff --git a/src/utils/claimDataMapper.ts b/src/utils/claimDataMapper.ts
index 5c8674e..3063132 100644
--- a/src/utils/claimDataMapper.ts
+++ b/src/utils/claimDataMapper.ts
@@ -175,14 +175,14 @@ export function mapToClaimManagementRequest(
// Get closed expenses breakdown from new completionExpenses table
const closedExpensesBreakdown = Array.isArray(completionExpenses) && completionExpenses.length > 0
? completionExpenses.map((exp: any) => ({
- description: exp.description || exp.itemDescription || '',
+ description: exp.description || exp.itemDescription || exp.item_description || '',
amount: Number(exp.amount) || 0,
- gstRate: exp.gstRate,
- gstAmt: exp.gstAmt,
- cgstAmt: exp.cgstAmt,
- sgstAmt: exp.sgstAmt,
- igstAmt: exp.igstAmt,
- totalAmt: exp.totalAmt
+ gstRate: exp.gstRate ?? exp.gst_rate,
+ gstAmt: exp.gstAmt ?? exp.gst_amt,
+ cgstAmt: exp.cgstAmt ?? exp.cgst_amt,
+ sgstAmt: exp.sgstAmt ?? exp.sgst_amt,
+ igstAmt: exp.igstAmt ?? exp.igst_amt,
+ totalAmt: exp.totalAmt ?? exp.total_amt
}))
: (completionDetails?.closedExpenses ||
completionDetails?.closed_expenses ||
@@ -232,14 +232,14 @@ export function mapToClaimManagementRequest(
proposalDocumentUrl: proposalDetails.proposalDocumentUrl || proposalDetails.proposal_document_url,
costBreakup: Array.isArray(proposalDetails.costBreakup || proposalDetails.cost_breakup)
? (proposalDetails.costBreakup || proposalDetails.cost_breakup).map((item: any) => ({
- description: item.description || '',
+ description: item.description || item.itemDescription || item.item_description || '',
amount: Number(item.amount) || 0,
- gstRate: item.gstRate,
- gstAmt: item.gstAmt,
- cgstAmt: item.cgstAmt,
- sgstAmt: item.sgstAmt,
- igstAmt: item.igstAmt,
- totalAmt: item.totalAmt
+ gstRate: item.gstRate ?? item.gst_rate,
+ gstAmt: item.gstAmt ?? item.gst_amt,
+ cgstAmt: item.cgstAmt ?? item.cgst_amt,
+ sgstAmt: item.sgstAmt ?? item.sgst_amt,
+ igstAmt: item.igstAmt ?? item.igst_amt,
+ totalAmt: item.totalAmt ?? item.total_amt
}))
: [],
totalEstimatedBudget: proposalDetails.totalEstimatedBudget || proposalDetails.total_estimated_budget || 0,