credit note invoice mapped with webhooks
This commit is contained in:
parent
ecf2556c64
commit
bbae59e271
@ -18,6 +18,12 @@ import {
|
|||||||
getRoleBasedVisibility,
|
getRoleBasedVisibility,
|
||||||
type RequestRole,
|
type RequestRole,
|
||||||
} from '@/utils/claimDataMapper';
|
} from '@/utils/claimDataMapper';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { RichTextEditor } from '@/components/ui/rich-text-editor';
|
||||||
|
import { FormattedDescription } from '@/components/common/FormattedDescription';
|
||||||
|
import { CheckCircle, RefreshCw, Loader2 } from 'lucide-react';
|
||||||
|
import { formatDateTime } from '@/utils/dateFormatter';
|
||||||
|
|
||||||
interface ClaimManagementOverviewTabProps {
|
interface ClaimManagementOverviewTabProps {
|
||||||
request: any; // Original request object
|
request: any; // Original request object
|
||||||
@ -26,6 +32,15 @@ interface ClaimManagementOverviewTabProps {
|
|||||||
isInitiator: boolean;
|
isInitiator: boolean;
|
||||||
onEditClaimAmount?: () => void;
|
onEditClaimAmount?: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
// Closure props
|
||||||
|
needsClosure?: boolean;
|
||||||
|
conclusionRemark?: string;
|
||||||
|
setConclusionRemark?: (value: string) => void;
|
||||||
|
conclusionLoading?: boolean;
|
||||||
|
conclusionSubmitting?: boolean;
|
||||||
|
aiGenerated?: boolean;
|
||||||
|
handleGenerateConclusion?: () => void;
|
||||||
|
handleFinalizeConclusion?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ClaimManagementOverviewTab({
|
export function ClaimManagementOverviewTab({
|
||||||
@ -35,6 +50,14 @@ export function ClaimManagementOverviewTab({
|
|||||||
isInitiator: _isInitiator,
|
isInitiator: _isInitiator,
|
||||||
onEditClaimAmount: _onEditClaimAmount,
|
onEditClaimAmount: _onEditClaimAmount,
|
||||||
className = '',
|
className = '',
|
||||||
|
needsClosure = false,
|
||||||
|
conclusionRemark = '',
|
||||||
|
setConclusionRemark,
|
||||||
|
conclusionLoading = false,
|
||||||
|
conclusionSubmitting = false,
|
||||||
|
aiGenerated = false,
|
||||||
|
handleGenerateConclusion,
|
||||||
|
handleFinalizeConclusion,
|
||||||
}: ClaimManagementOverviewTabProps) {
|
}: ClaimManagementOverviewTabProps) {
|
||||||
// Check if this is a claim management request
|
// Check if this is a claim management request
|
||||||
if (!isClaimManagementRequest(apiRequest)) {
|
if (!isClaimManagementRequest(apiRequest)) {
|
||||||
@ -98,6 +121,21 @@ export function ClaimManagementOverviewTab({
|
|||||||
phone: apiRequest.initiator?.phone || apiRequest.initiator?.mobile,
|
phone: apiRequest.initiator?.phone || apiRequest.initiator?.mobile,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Debug: Log closure props to help troubleshoot
|
||||||
|
console.debug('[ClaimManagementOverviewTab] Closure setup check:', {
|
||||||
|
needsClosure,
|
||||||
|
requestStatus: apiRequest?.status,
|
||||||
|
requestStatusLower: (apiRequest?.status || '').toLowerCase(),
|
||||||
|
hasConclusionRemark: !!conclusionRemark,
|
||||||
|
conclusionRemarkLength: conclusionRemark?.length || 0,
|
||||||
|
conclusionLoading,
|
||||||
|
conclusionSubmitting,
|
||||||
|
aiGenerated,
|
||||||
|
hasHandleGenerate: !!handleGenerateConclusion,
|
||||||
|
hasHandleFinalize: !!handleFinalizeConclusion,
|
||||||
|
hasSetConclusion: !!setConclusionRemark,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`space-y-6 ${className}`}>
|
<div className={`space-y-6 ${className}`}>
|
||||||
{/* Activity Information - Always visible */}
|
{/* Activity Information - Always visible */}
|
||||||
@ -118,6 +156,153 @@ export function ClaimManagementOverviewTab({
|
|||||||
|
|
||||||
{/* Request Initiator */}
|
{/* Request Initiator */}
|
||||||
<RequestInitiatorCard initiatorInfo={initiatorInfo} />
|
<RequestInitiatorCard initiatorInfo={initiatorInfo} />
|
||||||
|
|
||||||
|
{/* Closed Request Conclusion Remark Display */}
|
||||||
|
{apiRequest?.status === 'closed' && apiRequest?.conclusionRemark && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
|
<CheckCircle className="w-5 h-5 text-gray-600" />
|
||||||
|
Conclusion Remark
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="mt-1 text-xs sm:text-sm">Final summary of this closed request</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-4">
|
||||||
|
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4">
|
||||||
|
<FormattedDescription
|
||||||
|
content={apiRequest.conclusionRemark || ''}
|
||||||
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{apiRequest.closureDate && (
|
||||||
|
<div className="mt-3 flex items-center justify-between text-xs text-gray-500 border-t border-gray-200 pt-3">
|
||||||
|
<span>Request closed on {formatDateTime(apiRequest.closureDate)}</span>
|
||||||
|
<span>By {initiatorInfo.name}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Conclusion Remark Section - Closure Setup */}
|
||||||
|
{needsClosure && (
|
||||||
|
<Card data-testid="conclusion-remark-card">
|
||||||
|
<CardHeader className={`bg-gradient-to-r border-b ${
|
||||||
|
(apiRequest?.status || '').toLowerCase() === 'rejected'
|
||||||
|
? 'from-red-50 to-rose-50 border-red-200'
|
||||||
|
: 'from-green-50 to-emerald-50 border-green-200'
|
||||||
|
}`}>
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<CardTitle className={`flex items-center gap-2 text-base sm:text-lg ${
|
||||||
|
(apiRequest?.status || '').toLowerCase() === 'rejected' ? 'text-red-700' : 'text-green-700'
|
||||||
|
}`}>
|
||||||
|
<CheckCircle className={`w-5 h-5 ${
|
||||||
|
(apiRequest?.status || '').toLowerCase() === 'rejected' ? 'text-red-600' : 'text-green-600'
|
||||||
|
}`} />
|
||||||
|
Conclusion Remark - Final Step
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="mt-1 text-xs sm:text-sm">
|
||||||
|
{(apiRequest?.status || '').toLowerCase() === 'rejected'
|
||||||
|
? 'This request was rejected. Please review the AI-generated closure remark and finalize it to close this request.'
|
||||||
|
: 'All approvals are complete. Please review and finalize the conclusion to close this request.'}
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
{handleGenerateConclusion && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleGenerateConclusion}
|
||||||
|
disabled={conclusionLoading}
|
||||||
|
className="gap-2 shrink-0"
|
||||||
|
data-testid="generate-ai-conclusion-button"
|
||||||
|
>
|
||||||
|
<RefreshCw className={`w-3.5 h-3.5 ${conclusionLoading ? 'animate-spin' : ''}`} />
|
||||||
|
{aiGenerated ? 'Regenerate' : 'Generate with AI'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-4">
|
||||||
|
{conclusionLoading ? (
|
||||||
|
<div className="flex items-center justify-center py-8" data-testid="conclusion-loading">
|
||||||
|
<div className="text-center">
|
||||||
|
<Loader2 className="w-8 h-8 text-blue-600 animate-spin mx-auto mb-2" />
|
||||||
|
<p className="text-sm text-gray-600">Preparing conclusion remark...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<label className="text-sm font-medium text-gray-700">Conclusion Remark</label>
|
||||||
|
{aiGenerated && (
|
||||||
|
<span className="text-xs text-blue-600" data-testid="ai-generated-label">
|
||||||
|
✓ System-generated suggestion (editable)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{setConclusionRemark && (
|
||||||
|
<RichTextEditor
|
||||||
|
value={conclusionRemark}
|
||||||
|
onChange={(html) => setConclusionRemark(html)}
|
||||||
|
placeholder="Enter a professional conclusion remark summarizing the request outcome, key decisions, and approvals..."
|
||||||
|
className="text-sm"
|
||||||
|
minHeight="160px"
|
||||||
|
data-testid="conclusion-remark-textarea"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<p className="text-xs text-blue-600 mt-1">
|
||||||
|
💡 Tip: You can paste formatted content (lists, tables) and the formatting will be preserved.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between mt-2">
|
||||||
|
<p className="text-xs text-gray-500">This will be the final summary for this request</p>
|
||||||
|
<p className="text-xs text-gray-500" data-testid="character-count">
|
||||||
|
{conclusionRemark ? conclusionRemark.replace(/<[^>]*>/g, '').length : 0} / 2000 characters
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||||
|
<p className="text-xs sm:text-sm font-semibold text-blue-900 mb-1.5">Finalizing this request will:</p>
|
||||||
|
<ul className="text-xs sm:text-sm text-blue-800 space-y-0.5 pl-4">
|
||||||
|
<li className="list-disc">Change request status to "CLOSED"</li>
|
||||||
|
<li className="list-disc">Notify all participants of closure</li>
|
||||||
|
<li className="list-disc">Move request to Closed Requests</li>
|
||||||
|
<li className="list-disc">Save conclusion remark permanently</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{handleFinalizeConclusion && (
|
||||||
|
<div className="flex gap-3 justify-end pt-3 border-t">
|
||||||
|
<Button
|
||||||
|
onClick={handleFinalizeConclusion}
|
||||||
|
disabled={conclusionSubmitting || !conclusionRemark.trim()}
|
||||||
|
className="bg-green-600 hover:bg-green-700 text-white"
|
||||||
|
data-testid="finalize-close-button"
|
||||||
|
>
|
||||||
|
{conclusionSubmitting ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
Finalizing...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CheckCircle className="w-4 h-4 mr-2" />
|
||||||
|
Finalize & Close Request
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { CreditNoteSAPModal } from './modals';
|
|||||||
import { EmailNotificationTemplateModal } from './modals';
|
import { EmailNotificationTemplateModal } from './modals';
|
||||||
import { DMSPushModal } from './modals';
|
import { DMSPushModal } from './modals';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { submitProposal, updateIODetails, submitCompletion, updateEInvoice } from '@/services/dealerClaimApi';
|
import { submitProposal, updateIODetails, submitCompletion, updateEInvoice, sendCreditNoteToDealer } from '@/services/dealerClaimApi';
|
||||||
import { getWorkflowDetails, approveLevel, rejectLevel } from '@/services/workflowApi';
|
import { getWorkflowDetails, approveLevel, rejectLevel } from '@/services/workflowApi';
|
||||||
import { uploadDocument } from '@/services/documentApi';
|
import { uploadDocument } from '@/services/documentApi';
|
||||||
|
|
||||||
@ -1271,8 +1271,26 @@ export function DealerClaimWorkflowTab({
|
|||||||
toast.info('Download functionality will be implemented');
|
toast.info('Download functionality will be implemented');
|
||||||
}}
|
}}
|
||||||
onSendToDealer={async () => {
|
onSendToDealer={async () => {
|
||||||
// TODO: Implement send to dealer functionality
|
try {
|
||||||
toast.info('Send to dealer functionality will be implemented');
|
const requestId = request?.requestId || request?.id;
|
||||||
|
if (!requestId) {
|
||||||
|
toast.error('Request ID not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendCreditNoteToDealer(requestId);
|
||||||
|
|
||||||
|
toast.success('Credit note sent to dealer successfully. Step 8 has been approved.');
|
||||||
|
|
||||||
|
// Refresh the request details to show updated status
|
||||||
|
if (onRefresh) {
|
||||||
|
onRefresh();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Failed to send credit note to dealer:', error);
|
||||||
|
const errorMessage = error?.response?.data?.message || error?.message || 'Failed to send credit note to dealer';
|
||||||
|
toast.error(errorMessage);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
creditNoteData={{
|
creditNoteData={{
|
||||||
creditNoteNumber: (request as any)?.creditNote?.creditNoteNumber ||
|
creditNoteNumber: (request as any)?.creditNote?.creditNoteNumber ||
|
||||||
@ -1295,7 +1313,9 @@ export function DealerClaimWorkflowTab({
|
|||||||
Number((request as any)?.claimDetails?.creditNoteAmount) :
|
Number((request as any)?.claimDetails?.creditNoteAmount) :
|
||||||
((request as any)?.claimDetails?.credit_note_amount ?
|
((request as any)?.claimDetails?.credit_note_amount ?
|
||||||
Number((request as any)?.claimDetails?.credit_note_amount) : undefined)))),
|
Number((request as any)?.claimDetails?.credit_note_amount) : undefined)))),
|
||||||
status: 'APPROVED',
|
status: (request as any)?.creditNote?.status ||
|
||||||
|
(request as any)?.claimDetails?.creditNote?.status ||
|
||||||
|
((request as any)?.creditNote?.creditNoteNumber ? 'CONFIRMED' : 'PENDING'),
|
||||||
}}
|
}}
|
||||||
dealerInfo={{
|
dealerInfo={{
|
||||||
dealerName: (request as any)?.claimDetails?.dealerName || (request as any)?.claimDetails?.dealer_name,
|
dealerName: (request as any)?.claimDetails?.dealerName || (request as any)?.claimDetails?.dealer_name,
|
||||||
|
|||||||
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* CloserCard Component
|
||||||
|
* Displays who closed the request and closure details
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||||
|
import { Mail, Calendar, FileText } from 'lucide-react';
|
||||||
|
import { formatDateTime } from '@/utils/dateFormatter';
|
||||||
|
|
||||||
|
interface CloserInfo {
|
||||||
|
name?: string;
|
||||||
|
role?: string;
|
||||||
|
department?: string;
|
||||||
|
email?: string;
|
||||||
|
closureDate?: string;
|
||||||
|
conclusionRemark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CloserCardProps {
|
||||||
|
closerInfo: CloserInfo;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CloserCard({ closerInfo, className }: CloserCardProps) {
|
||||||
|
// If no closure date, don't render
|
||||||
|
if (!closerInfo.closureDate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate initials from name
|
||||||
|
const getInitials = (name?: string) => {
|
||||||
|
if (!name) return 'CL';
|
||||||
|
return name
|
||||||
|
.split(' ')
|
||||||
|
.map((n) => n[0])
|
||||||
|
.join('')
|
||||||
|
.toUpperCase()
|
||||||
|
.slice(0, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closerName = closerInfo.name || 'System';
|
||||||
|
const hasCloserInfo = closerInfo.name || closerInfo.email;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={className}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">Request Closer</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{hasCloserInfo ? (
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<Avatar className="h-14 w-14 ring-2 ring-white shadow-md">
|
||||||
|
<AvatarFallback className="bg-green-700 text-white font-semibold text-lg">
|
||||||
|
{getInitials(closerInfo.name)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-semibold text-gray-900">{closerName}</h3>
|
||||||
|
{closerInfo.role && (
|
||||||
|
<p className="text-sm text-gray-600">{closerInfo.role}</p>
|
||||||
|
)}
|
||||||
|
{closerInfo.department && (
|
||||||
|
<p className="text-sm text-gray-500">{closerInfo.department}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-3 space-y-2">
|
||||||
|
{/* Email */}
|
||||||
|
{closerInfo.email && (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<Mail className="w-4 h-4" />
|
||||||
|
<span>{closerInfo.email}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Closure Date */}
|
||||||
|
{closerInfo.closureDate && (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<Calendar className="w-4 h-4" />
|
||||||
|
<span>Closed on {formatDateTime(closerInfo.closureDate, { includeTime: true, format: 'short' })}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm text-gray-600">Request closed by system</p>
|
||||||
|
{closerInfo.closureDate && (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<Calendar className="w-4 h-4" />
|
||||||
|
<span>Closed on {formatDateTime(closerInfo.closureDate, { includeTime: true, format: 'short' })}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Conclusion Remark */}
|
||||||
|
{closerInfo.conclusionRemark && (
|
||||||
|
<div className="mt-4 pt-4 border-t border-gray-200">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<FileText className="w-4 h-4 text-gray-500 mt-0.5 flex-shrink-0" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-xs font-medium text-gray-600 uppercase tracking-wider mb-1">
|
||||||
|
Conclusion Remark
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-700 whitespace-pre-wrap">
|
||||||
|
{closerInfo.conclusionRemark}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@ -10,3 +10,4 @@ export { DealerInformationCard } from './DealerInformationCard';
|
|||||||
export { ProcessDetailsCard } from './ProcessDetailsCard';
|
export { ProcessDetailsCard } from './ProcessDetailsCard';
|
||||||
export { ProposalDetailsCard } from './ProposalDetailsCard';
|
export { ProposalDetailsCard } from './ProposalDetailsCard';
|
||||||
export { RequestInitiatorCard } from './RequestInitiatorCard';
|
export { RequestInitiatorCard } from './RequestInitiatorCard';
|
||||||
|
export { CloserCard } from './CloserCard';
|
||||||
|
|||||||
@ -57,12 +57,13 @@ export function CreditNoteSAPModal({
|
|||||||
const [downloading, setDownloading] = useState(false);
|
const [downloading, setDownloading] = useState(false);
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
|
|
||||||
const creditNoteNumber = creditNoteData?.creditNoteNumber || 'CN-RE-REQ-2024-CM-101-312580';
|
const hasCreditNote = creditNoteData?.creditNoteNumber && creditNoteData?.creditNoteNumber !== '';
|
||||||
|
const creditNoteNumber = creditNoteData?.creditNoteNumber || '';
|
||||||
const creditNoteDate = creditNoteData?.creditNoteDate
|
const creditNoteDate = creditNoteData?.creditNoteDate
|
||||||
? formatDateTime(creditNoteData.creditNoteDate, { includeTime: false, format: 'short' })
|
? formatDateTime(creditNoteData.creditNoteDate, { includeTime: false, format: 'short' })
|
||||||
: 'Dec 5, 2025';
|
: '';
|
||||||
const creditNoteAmount = creditNoteData?.creditNoteAmount || 800;
|
const creditNoteAmount = creditNoteData?.creditNoteAmount || 0;
|
||||||
const status = creditNoteData?.status || 'APPROVED';
|
const status = creditNoteData?.status || 'PENDING';
|
||||||
|
|
||||||
const dealerName = dealerInfo?.dealerName || 'Jaipur Royal Enfield';
|
const dealerName = dealerInfo?.dealerName || 'Jaipur Royal Enfield';
|
||||||
const dealerCode = dealerInfo?.dealerCode || 'RE-JP-009';
|
const dealerCode = dealerInfo?.dealerCode || 'RE-JP-009';
|
||||||
@ -127,6 +128,8 @@ export function CreditNoteSAPModal({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-5 py-4">
|
<div className="space-y-5 py-4">
|
||||||
|
{hasCreditNote ? (
|
||||||
|
<>
|
||||||
{/* Credit Note Document Card */}
|
{/* Credit Note Document Card */}
|
||||||
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-lg p-6">
|
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-lg p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
@ -136,7 +139,7 @@ export function CreditNoteSAPModal({
|
|||||||
</div>
|
</div>
|
||||||
<Badge className="bg-green-600 text-white px-4 py-2 text-base">
|
<Badge className="bg-green-600 text-white px-4 py-2 text-base">
|
||||||
<CircleCheckBig className="w-4 h-4 mr-2" />
|
<CircleCheckBig className="w-4 h-4 mr-2" />
|
||||||
{status === 'APPROVED' ? 'Approved' : status === 'ISSUED' ? 'Issued' : status === 'SENT' ? 'Sent' : 'Pending'}
|
{status === 'APPROVED' || status === 'CONFIRMED' ? 'Approved' : status === 'ISSUED' ? 'Issued' : status === 'SENT' ? 'Sent' : 'Pending'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4 mt-4">
|
<div className="grid grid-cols-2 gap-4 mt-4">
|
||||||
@ -165,6 +168,23 @@ export function CreditNoteSAPModal({
|
|||||||
</Label>
|
</Label>
|
||||||
<p className="text-4xl font-bold text-blue-700">{formatCurrency(creditNoteAmount)}</p>
|
<p className="text-4xl font-bold text-blue-700">{formatCurrency(creditNoteAmount)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
/* No Credit Note Available */
|
||||||
|
<div className="bg-gray-50 border-2 border-gray-300 rounded-lg p-8 text-center">
|
||||||
|
<div className="flex flex-col items-center justify-center space-y-4">
|
||||||
|
<div className="w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center">
|
||||||
|
<Receipt className="w-8 h-8 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-700 mb-2">No Credit Note Available</h3>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Credit note has not been generated yet. Please wait for the credit note to be generated from DMS.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Dealer Information */}
|
{/* Dealer Information */}
|
||||||
<div className="bg-purple-50 border-2 border-purple-200 rounded-lg p-5">
|
<div className="bg-purple-50 border-2 border-purple-200 rounded-lg p-5">
|
||||||
@ -244,6 +264,8 @@ export function CreditNoteSAPModal({
|
|||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
{hasCreditNote && (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
@ -261,6 +283,8 @@ export function CreditNoteSAPModal({
|
|||||||
<Send className="w-4 h-4 mr-2" />
|
<Send className="w-4 h-4 mr-2" />
|
||||||
{sending ? 'Sending...' : 'Send to Dealer'}
|
{sending ? 'Sending...' : 'Send to Dealer'}
|
||||||
</Button>
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import { useRequestDetails } from '@/hooks/useRequestDetails';
|
|||||||
import { useRequestSocket } from '@/hooks/useRequestSocket';
|
import { useRequestSocket } from '@/hooks/useRequestSocket';
|
||||||
import { useDocumentUpload } from '@/hooks/useDocumentUpload';
|
import { useDocumentUpload } from '@/hooks/useDocumentUpload';
|
||||||
import { useModalManager } from '@/hooks/useModalManager';
|
import { useModalManager } from '@/hooks/useModalManager';
|
||||||
|
import { useConclusionRemark } from '@/hooks/useConclusionRemark';
|
||||||
import { downloadDocument } from '@/services/workflowApi';
|
import { downloadDocument } from '@/services/workflowApi';
|
||||||
|
|
||||||
// Dealer Claim Components (import from index to get properly aliased exports)
|
// Dealer Claim Components (import from index to get properly aliased exports)
|
||||||
@ -218,6 +219,37 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
handleAddSpectator,
|
handleAddSpectator,
|
||||||
} = useModalManager(requestIdentifier, currentApprovalLevel, refreshDetails);
|
} = useModalManager(requestIdentifier, currentApprovalLevel, refreshDetails);
|
||||||
|
|
||||||
|
// Closure functionality - only for initiator when request is approved/rejected
|
||||||
|
// Check both lowercase and uppercase status values
|
||||||
|
const requestStatus = (request?.status || apiRequest?.status || '').toLowerCase();
|
||||||
|
const needsClosure = (requestStatus === 'approved' || requestStatus === 'rejected') && isInitiator;
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
console.debug('[DealerClaimRequestDetail] Closure check:', {
|
||||||
|
requestStatus,
|
||||||
|
requestStatusRaw: request?.status,
|
||||||
|
apiRequestStatusRaw: apiRequest?.status,
|
||||||
|
isInitiator,
|
||||||
|
needsClosure,
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
conclusionRemark,
|
||||||
|
setConclusionRemark,
|
||||||
|
conclusionLoading,
|
||||||
|
conclusionSubmitting,
|
||||||
|
aiGenerated,
|
||||||
|
handleGenerateConclusion,
|
||||||
|
handleFinalizeConclusion,
|
||||||
|
} = useConclusionRemark(
|
||||||
|
request,
|
||||||
|
requestIdentifier,
|
||||||
|
isInitiator,
|
||||||
|
refreshDetails,
|
||||||
|
onBack,
|
||||||
|
setActionStatus,
|
||||||
|
setShowActionStatusModal
|
||||||
|
);
|
||||||
|
|
||||||
// Auto-switch tab when URL query parameter changes
|
// Auto-switch tab when URL query parameter changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
@ -500,6 +532,14 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
apiRequest={apiRequest}
|
apiRequest={apiRequest}
|
||||||
currentUserId={currentUserId}
|
currentUserId={currentUserId}
|
||||||
isInitiator={isInitiator}
|
isInitiator={isInitiator}
|
||||||
|
needsClosure={needsClosure}
|
||||||
|
conclusionRemark={conclusionRemark}
|
||||||
|
setConclusionRemark={setConclusionRemark}
|
||||||
|
conclusionLoading={conclusionLoading}
|
||||||
|
conclusionSubmitting={conclusionSubmitting}
|
||||||
|
aiGenerated={aiGenerated}
|
||||||
|
handleGenerateConclusion={handleGenerateConclusion}
|
||||||
|
handleFinalizeConclusion={handleFinalizeConclusion}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@ -290,3 +290,17 @@ export async function updateCreditNote(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send credit note to dealer and auto-approve Step 8
|
||||||
|
* POST /api/v1/dealer-claims/:requestId/credit-note/send
|
||||||
|
*/
|
||||||
|
export async function sendCreditNoteToDealer(requestId: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.post(`/dealer-claims/${requestId}/credit-note/send`);
|
||||||
|
return response.data?.data || response.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[DealerClaimAPI] Error sending credit note to dealer:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user