form 16 remote code pulled
This commit is contained in:
commit
048b1bf751
4
.gitignore
vendored
4
.gitignore
vendored
@ -45,3 +45,7 @@ coverage
|
||||
.turbo
|
||||
*.tsbuildinfo
|
||||
|
||||
|
||||
FORM16_SAP_ACTUAL_IMPLEMENTATION_ANALYSIS.txt
|
||||
FORM16_SAP_FIELDS_ONLY.txt
|
||||
form16_use_cases_report.txt
|
||||
|
||||
@ -351,31 +351,11 @@ export function Form16AdminConfig() {
|
||||
<div className="flex-1 min-w-0 space-y-3">
|
||||
<p className="font-medium">26AS data added</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium text-foreground">Sent to:</span> RE users who can view 26AS, and separately to all dealers. <span className="font-medium text-foreground">When:</span> As soon as new 26AS data is uploaded.
|
||||
<span className="font-medium text-foreground">Sent to:</span> RE users who can view 26AS. <span className="font-medium text-foreground">When:</span> As soon as new 26AS data is uploaded.
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Notification messages use a fixed system template and cannot be edited here.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<Label className="text-xs text-muted-foreground">Message to RE users</Label>
|
||||
<Textarea
|
||||
rows={2}
|
||||
value={notification26AsDataAdded.templateRe ?? ''}
|
||||
onChange={(e) => setNotification26AsDataAdded((prev) => ({ ...prev, templateRe: e.target.value }))}
|
||||
className="resize-none text-sm mt-1"
|
||||
placeholder="e.g. 26AS data has been added. Please review..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs text-muted-foreground">Message to dealers</Label>
|
||||
<Textarea
|
||||
rows={2}
|
||||
value={notification26AsDataAdded.templateDealers ?? ''}
|
||||
onChange={(e) => setNotification26AsDataAdded((prev) => ({ ...prev, templateDealers: e.target.value }))}
|
||||
className="resize-none text-sm mt-1"
|
||||
placeholder="e.g. New 26AS data has been uploaded. You can submit Form 16..."
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">Placeholders: [Name], [Request ID]</p>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={notification26AsDataAdded.enabled}
|
||||
@ -390,16 +370,9 @@ export function Form16AdminConfig() {
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium text-foreground">Sent to:</span> The dealer who submitted the Form 16. <span className="font-medium text-foreground">When:</span> Immediately after their submission is matched with 26AS and a credit note is generated.
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
<Textarea
|
||||
rows={2}
|
||||
value={notificationForm16SuccessCreditNote.template ?? ''}
|
||||
onChange={(e) => setNotificationForm16SuccessCreditNote((prev) => ({ ...prev, template: e.target.value }))}
|
||||
className="resize-none text-sm"
|
||||
placeholder="Message template..."
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Placeholders: [Name], [CreditNoteRef], [Request ID]</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Notification message is fixed in the system configuration.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={notificationForm16SuccessCreditNote.enabled}
|
||||
@ -414,16 +387,9 @@ export function Form16AdminConfig() {
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="font-medium text-foreground">Sent to:</span> The dealer who submitted. <span className="font-medium text-foreground">When:</span> When their submission fails (e.g. value mismatch with 26AS, duplicate, or validation error) so they can correct and resubmit.
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
<Textarea
|
||||
rows={2}
|
||||
value={notificationForm16Unsuccessful.template ?? ''}
|
||||
onChange={(e) => setNotificationForm16Unsuccessful((prev) => ({ ...prev, template: e.target.value }))}
|
||||
className="resize-none text-sm"
|
||||
placeholder="Message template..."
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Placeholders: [Name], [Issue], [Request ID]</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Notification message is fixed in the system configuration.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={notificationForm16Unsuccessful.enabled}
|
||||
@ -483,16 +449,9 @@ export function Form16AdminConfig() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Textarea
|
||||
rows={2}
|
||||
value={alertSubmitForm16Template}
|
||||
onChange={(e) => setAlertSubmitForm16Template(e.target.value)}
|
||||
className="resize-none text-sm mt-1"
|
||||
placeholder="Message template for alert to dealers..."
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Placeholders: [Name], [DueDate], [Request ID]</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Alert message content is fixed and managed by the system.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
@ -553,16 +512,9 @@ export function Form16AdminConfig() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Textarea
|
||||
rows={2}
|
||||
value={reminderNotificationTemplate}
|
||||
onChange={(e) => setReminderNotificationTemplate(e.target.value)}
|
||||
className="resize-none text-sm mt-1"
|
||||
placeholder="Message template for reminder to dealers..."
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Placeholders: [Name], [Request ID], [Status], [TAT]</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Reminder message content is fixed and managed by the system.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
|
||||
@ -7,22 +7,11 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Loader2, Receipt, X, RotateCcw } from 'lucide-react';
|
||||
import {
|
||||
getCreditNoteByRequestId,
|
||||
cancelForm16Submission,
|
||||
setForm16ResubmissionNeeded,
|
||||
generateForm16CreditNoteManually,
|
||||
} from '@/services/form16Api';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
@ -35,14 +24,11 @@ interface Form16QuickActionsProps {
|
||||
export function Form16QuickActions({ requestId, request, onRefresh }: Form16QuickActionsProps) {
|
||||
const [creditNote, setCreditNote] = useState<{ id: number; status: string } | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [actionLoading, setActionLoading] = useState<'cancel' | 'resubmit' | 'credit' | null>(null);
|
||||
const [generateCnOpen, setGenerateCnOpen] = useState(false);
|
||||
const [generateCnAmount, setGenerateCnAmount] = useState('');
|
||||
const [actionLoading, setActionLoading] = useState<'cancel' | 'resubmit' | null>(null);
|
||||
|
||||
const form16 = request?.form16Submission;
|
||||
const hasSubmission = !!form16;
|
||||
const hasCreditNote = !!creditNote && creditNote.status !== 'withdrawn';
|
||||
const suggestedAmount = form16?.tdsAmount != null ? Number(form16.tdsAmount) : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (!requestId) {
|
||||
@ -91,32 +77,9 @@ export function Form16QuickActions({ requestId, request, onRefresh }: Form16Quic
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateCreditNote = async () => {
|
||||
const amount = parseFloat(generateCnAmount);
|
||||
if (!requestId || Number.isNaN(amount) || amount <= 0) {
|
||||
toast.error('Enter a valid amount to generate credit note');
|
||||
return;
|
||||
}
|
||||
setActionLoading('credit');
|
||||
try {
|
||||
await generateForm16CreditNoteManually(requestId, amount);
|
||||
setGenerateCnOpen(false);
|
||||
setGenerateCnAmount('');
|
||||
toast.success('Credit note generated (manually approved)');
|
||||
const note = await getCreditNoteByRequestId(requestId);
|
||||
setCreditNote(note ? { id: typeof note.id === 'number' ? note.id : Number(note.id), status: note.status || '' } : null);
|
||||
onRefresh?.();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : 'Failed to generate credit note');
|
||||
} finally {
|
||||
setActionLoading(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading || !hasSubmission || hasCreditNote) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="border-blue-200 bg-blue-50/30" data-testid="form16-quick-actions-card">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm flex items-center gap-2 text-blue-800">
|
||||
@ -124,7 +87,7 @@ export function Form16QuickActions({ requestId, request, onRefresh }: Form16Quic
|
||||
Form 16 actions
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs text-gray-600">
|
||||
View the document in the Documents tab. Cancel submission, mark resubmission needed, or generate credit note (e.g. when OCR was partial).
|
||||
View the document in the Documents tab. Cancel submission or mark resubmission needed.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
@ -148,58 +111,7 @@ export function Form16QuickActions({ requestId, request, onRefresh }: Form16Quic
|
||||
{actionLoading === 'resubmit' ? <Loader2 className="w-3 h-3 animate-spin mr-1" /> : <RotateCcw className="w-3 h-3 mr-1" />}
|
||||
Resubmission needed
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full justify-start border-emerald-300 text-emerald-700 hover:bg-emerald-50"
|
||||
onClick={() => {
|
||||
setGenerateCnAmount(suggestedAmount != null ? String(suggestedAmount) : '');
|
||||
setGenerateCnOpen(true);
|
||||
}}
|
||||
disabled={!!actionLoading}
|
||||
>
|
||||
{actionLoading === 'credit' ? <Loader2 className="w-3 h-3 animate-spin mr-1" /> : <Receipt className="w-3 h-3 mr-1" />}
|
||||
Generate credit note
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog open={generateCnOpen} onOpenChange={setGenerateCnOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Generate credit note (manual)</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter the amount for the credit note. This will mark the Form 16 as manually approved.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="form16-cn-amount">Amount (₹)</Label>
|
||||
<Input
|
||||
id="form16-cn-amount"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder={suggestedAmount != null ? String(suggestedAmount) : '0'}
|
||||
value={generateCnAmount}
|
||||
onChange={(e) => setGenerateCnAmount(e.target.value)}
|
||||
/>
|
||||
{suggestedAmount != null && (
|
||||
<p className="text-xs text-gray-500">Suggested from submission TDS amount: ₹{suggestedAmount.toLocaleString('en-IN')}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setGenerateCnOpen(false)} disabled={!!actionLoading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleGenerateCreditNote} disabled={!!actionLoading || !generateCnAmount.trim()}>
|
||||
{actionLoading === 'credit' ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : null}
|
||||
Generate credit note
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -132,18 +132,6 @@ export async function setForm16ResubmissionNeeded(requestId: string): Promise<vo
|
||||
await apiClient.post(`/form16/requests/${encodeURIComponent(requestId)}/resubmission-needed`);
|
||||
}
|
||||
|
||||
/** RE only. Manually generate credit note for Form 16 request. Body: { amount: number }. */
|
||||
export async function generateForm16CreditNoteManually(requestId: string, amount: number): Promise<{ creditNote: Form16CreditNoteItem }> {
|
||||
const { data } = await apiClient.post<{ data?: { creditNote?: Form16CreditNoteItem }; creditNote?: Form16CreditNoteItem }>(
|
||||
`/form16/requests/${encodeURIComponent(requestId)}/generate-credit-note`,
|
||||
{ amount }
|
||||
);
|
||||
const payload = data?.data ?? data;
|
||||
const creditNote = payload?.creditNote;
|
||||
if (!creditNote) throw new Error('Credit note not returned');
|
||||
return { creditNote };
|
||||
}
|
||||
|
||||
/** Get a single credit note by id with dealer info and dealer transaction history. */
|
||||
export interface CreditNoteDetailResponse {
|
||||
creditNote: Form16CreditNoteItem & { submission?: { submittedDate?: string | null } };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user