form 16 remote code pulled

This commit is contained in:
laxmanhalaki 2026-03-12 17:03:20 +05:30
commit 048b1bf751
4 changed files with 54 additions and 198 deletions

4
.gitignore vendored
View File

@ -45,3 +45,7 @@ coverage
.turbo
*.tsbuildinfo
FORM16_SAP_ACTUAL_IMPLEMENTATION_ANALYSIS.txt
FORM16_SAP_FIELDS_ONLY.txt
form16_use_cases_report.txt

View File

@ -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

View File

@ -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>
</>
);
}

View File

@ -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 } };