Compare commits

..

2 Commits

Author SHA1 Message Date
Aaditya Jaiswal
6d8a60581d Merge branch 'laxman_dev' updates
Made-with: Cursor
2026-03-24 11:12:39 +05:30
Aaditya Jaiswal
3a6a8c2472 Fixed claim number issue at debit note and SAP responses from database 2026-03-24 11:04:49 +05:30
5 changed files with 92 additions and 160 deletions

View File

@ -132,12 +132,12 @@ export function Form16AdminConfig() {
const [alertSubmitForm16FrequencyDays, setAlertSubmitForm16FrequencyDays] = useState(0); const [alertSubmitForm16FrequencyDays, setAlertSubmitForm16FrequencyDays] = useState(0);
const [alertSubmitForm16FrequencyHours, setAlertSubmitForm16FrequencyHours] = useState(24); const [alertSubmitForm16FrequencyHours, setAlertSubmitForm16FrequencyHours] = useState(24);
const [alertSubmitForm16RunAtTime, setAlertSubmitForm16RunAtTime] = useState('09:00'); const [alertSubmitForm16RunAtTime, setAlertSubmitForm16RunAtTime] = useState('09:00');
const [alertSubmitForm16Template, setAlertSubmitForm16Template] = useState('Please submit your Form 16 at your earliest. [Name], due date: [DueDate].'); const [alertSubmitForm16Template, setAlertSubmitForm16Template] = useState('Dear [Name], please submit Form 16A for the pending period. Due: [DueDate].');
const [reminderNotificationEnabled, setReminderNotificationEnabled] = useState(true); const [reminderNotificationEnabled, setReminderNotificationEnabled] = useState(true);
const [reminderFrequencyDays, setReminderFrequencyDays] = useState(0); const [reminderFrequencyDays, setReminderFrequencyDays] = useState(0);
const [reminderFrequencyHours, setReminderFrequencyHours] = useState(12); const [reminderFrequencyHours, setReminderFrequencyHours] = useState(12);
const [reminderRunAtTime, setReminderRunAtTime] = useState('10:00'); const [reminderRunAtTime, setReminderRunAtTime] = useState('10:00');
const [reminderNotificationTemplate, setReminderNotificationTemplate] = useState('Reminder: Form 16 submission is pending. [Name], [Request ID]. Please review.'); const [reminderNotificationTemplate, setReminderNotificationTemplate] = useState('Reminder: Dear [Name], your Form 16A submission is pending for request [Request ID]. Please complete it.');
useEffect(() => { useEffect(() => {
let mounted = true; let mounted = true;
@ -166,12 +166,12 @@ export function Form16AdminConfig() {
setAlertSubmitForm16FrequencyDays(config.alertSubmitForm16FrequencyDays ?? 0); setAlertSubmitForm16FrequencyDays(config.alertSubmitForm16FrequencyDays ?? 0);
setAlertSubmitForm16FrequencyHours(config.alertSubmitForm16FrequencyHours ?? 24); setAlertSubmitForm16FrequencyHours(config.alertSubmitForm16FrequencyHours ?? 24);
setAlertSubmitForm16RunAtTime(config.alertSubmitForm16RunAtTime !== undefined && config.alertSubmitForm16RunAtTime !== null ? config.alertSubmitForm16RunAtTime : '09:00'); setAlertSubmitForm16RunAtTime(config.alertSubmitForm16RunAtTime !== undefined && config.alertSubmitForm16RunAtTime !== null ? config.alertSubmitForm16RunAtTime : '09:00');
setAlertSubmitForm16Template(config.alertSubmitForm16Template ?? 'Please submit your Form 16 at your earliest. [Name], due date: [DueDate].'); setAlertSubmitForm16Template(config.alertSubmitForm16Template ?? 'Dear [Name], please submit Form 16A for the pending period. Due: [DueDate].');
setReminderNotificationEnabled(config.reminderNotificationEnabled ?? true); setReminderNotificationEnabled(config.reminderNotificationEnabled ?? true);
setReminderFrequencyDays(config.reminderFrequencyDays ?? 0); setReminderFrequencyDays(config.reminderFrequencyDays ?? 0);
setReminderFrequencyHours(config.reminderFrequencyHours ?? 12); setReminderFrequencyHours(config.reminderFrequencyHours ?? 12);
setReminderRunAtTime(config.reminderRunAtTime !== undefined && config.reminderRunAtTime !== null ? config.reminderRunAtTime : '10:00'); setReminderRunAtTime(config.reminderRunAtTime !== undefined && config.reminderRunAtTime !== null ? config.reminderRunAtTime : '10:00');
setReminderNotificationTemplate(config.reminderNotificationTemplate ?? 'Reminder: Form 16 submission is pending. [Name], [Request ID]. Please review.'); setReminderNotificationTemplate(config.reminderNotificationTemplate ?? 'Reminder: Dear [Name], your Form 16A submission is pending for request [Request ID]. Please complete it.');
}) })
.catch(() => { .catch(() => {
if (mounted) toast.error('Failed to load Form 16 configuration'); if (mounted) toast.error('Failed to load Form 16 configuration');

View File

@ -6,9 +6,8 @@ import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { ArrowLeft, Mail, Download, Loader2, RefreshCw, FileText, IndianRupee, CalendarClock } from 'lucide-react'; import { ArrowLeft, Mail, Download, Loader2, RefreshCw, FileText, IndianRupee, CalendarClock } from 'lucide-react';
import { getCreditNoteById, getCreditNoteDownloadUrl, type CreditNoteDetailResponse } from '@/services/form16Api'; import { getCreditNoteById, getCreditNoteSapResponse, type CreditNoteDetailResponse, type Form16SapResponseRecord } from '@/services/form16Api';
import { toast } from 'sonner'; import { toast } from 'sonner';
import apiClient from '@/services/authApi';
function formatDate(value: string | null | undefined): string { function formatDate(value: string | null | undefined): string {
if (!value) return ''; if (!value) return '';
@ -25,41 +24,14 @@ function formatAmount(value: number | null | undefined): string {
return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', maximumFractionDigits: 0 }).format(value); return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', maximumFractionDigits: 0 }).format(value);
} }
function buildAbsoluteBackendUrl(url: string): string { function mapSapResponseToPreviewRow(sap: Form16SapResponseRecord): Record<string, string> {
if (!url) return url; return {
if (/^https?:\/\//i.test(url)) return url; TRNS_UNIQ_NO: sap.trnsUniqNo || '',
const apiBase = ((import.meta as any).env?.VITE_API_BASE_URL as string | undefined) || 'http://localhost:5000/api/v1'; TDS_TRNS_ID: sap.tdsTransId || sap.claimNumber || '',
const origin = apiBase.replace(/\/api\/v1\/?$/i, ''); DOC_NO: sap.sapDocumentNumber || '',
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`; MSG_TYP: sap.msgTyp || '',
} MESSAGE: sap.message || '',
function parseSapResponseCsv(rawCsv: string): Record<string, string> | null {
const lines = rawCsv
.split(/\r?\n/)
.map((l) => l.trim())
.filter(Boolean);
if (lines.length < 2) return null;
const header = lines[0]!.split('|').map((h) => h.trim());
const isUsefulRow = (values: string[]) => {
const obj: Record<string, string> = {};
header.forEach((h, idx) => (obj[h] = (values[idx] || '').trim()));
const trns = (obj.TRNS_UNIQ_NO || '').trim();
const docNo = (obj.DOC_NO || '').trim();
const msgTyp = (obj.MSG_TYP || '').trim();
const tdsId = (obj.TDS_TRNS_ID || '').trim();
if (trns) return true;
if (tdsId && (docNo || msgTyp) && tdsId.toUpperCase() !== 'MSG_TYP' && tdsId.toUpperCase() !== 'MESSAGE') return true;
return false;
}; };
for (let i = lines.length - 1; i >= 1; i--) {
const values = lines[i]!.split('|');
if (isUsefulRow(values)) {
const obj: Record<string, string> = {};
header.forEach((h, idx) => (obj[h] = (values[idx] || '').trim()));
return obj;
}
}
return null;
} }
function formatIssuedAt(value: string | null | undefined): string { function formatIssuedAt(value: string | null | undefined): string {
@ -83,7 +55,7 @@ export function Form16CreditNoteDetail() {
const [previewOpen, setPreviewOpen] = useState(false); const [previewOpen, setPreviewOpen] = useState(false);
const [previewLoading, setPreviewLoading] = useState(false); const [previewLoading, setPreviewLoading] = useState(false);
const [previewRow, setPreviewRow] = useState<Record<string, string> | null>(null); const [previewRow, setPreviewRow] = useState<Record<string, string> | null>(null);
const [previewCsv, setPreviewCsv] = useState<string>(''); const [previewDownloadUrl, setPreviewDownloadUrl] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
const numId = id ? parseInt(id, 10) : NaN; const numId = id ? parseInt(id, 10) : NaN;
@ -134,18 +106,14 @@ export function Form16CreditNoteDetail() {
const numId = id ? parseInt(id, 10) : NaN; const numId = id ? parseInt(id, 10) : NaN;
if (Number.isNaN(numId)) return; if (Number.isNaN(numId)) return;
try { try {
const url = await getCreditNoteDownloadUrl(numId);
setPreviewOpen(true); setPreviewOpen(true);
setPreviewLoading(true); setPreviewLoading(true);
setPreviewRow(null); setPreviewRow(null);
setPreviewCsv(''); setPreviewDownloadUrl(null);
const absUrl = buildAbsoluteBackendUrl(url); const payload = await getCreditNoteSapResponse(numId);
const res = await apiClient.get<Blob>(absUrl, { responseType: 'blob' }); const row = mapSapResponseToPreviewRow(payload.sapResponse);
const rawCsv = await res.data.text();
const row = parseSapResponseCsv(rawCsv);
if (!row) throw new Error('Could not parse SAP response CSV');
setPreviewRow(row); setPreviewRow(row);
setPreviewCsv(rawCsv); setPreviewDownloadUrl(payload.url || null);
} catch (e: any) { } catch (e: any) {
const msg = const msg =
e?.response?.status === 409 e?.response?.status === 409
@ -184,7 +152,7 @@ export function Form16CreditNoteDetail() {
setPreviewOpen(open); setPreviewOpen(open);
if (!open) { if (!open) {
setPreviewRow(null); setPreviewRow(null);
setPreviewCsv(''); setPreviewDownloadUrl(null);
} }
}} }}
> >
@ -268,19 +236,13 @@ export function Form16CreditNoteDetail() {
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
if (!previewCsv) return; if (!previewDownloadUrl) {
const fileName = `${creditNote.creditNoteNumber || `credit-note-${id}`}.csv`; toast.error('CSV download link not available');
const blob = new Blob([previewCsv], { type: 'text/csv;charset=utf-8' }); return;
const href = URL.createObjectURL(blob); }
const a = document.createElement('a'); window.open(previewDownloadUrl, '_blank');
a.href = href;
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(href);
}} }}
disabled={!previewRow || previewLoading} disabled={!previewRow || previewLoading || !previewDownloadUrl}
> >
<Download className="w-4 h-4 mr-2" /> <Download className="w-4 h-4 mr-2" />
Download CSV Download CSV

View File

@ -8,8 +8,9 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
import { Search, Loader2, Receipt, Download, RefreshCw, FileText, IndianRupee, CalendarClock } from 'lucide-react'; import { Search, Loader2, Receipt, Download, RefreshCw, FileText, IndianRupee, CalendarClock } from 'lucide-react';
import { import {
listCreditNotes, listCreditNotes,
getCreditNoteDownloadUrl, getCreditNoteSapResponse,
type Form16CreditNoteItem, type Form16CreditNoteItem,
type Form16SapResponseRecord,
type ListCreditNotesParams, type ListCreditNotesParams,
type ListCreditNotesSummary, type ListCreditNotesSummary,
} from '@/services/form16Api'; } from '@/services/form16Api';
@ -38,9 +39,8 @@ const DEFAULT_SUMMARY: ListCreditNotesSummary = {
}; };
type SapResponsePreview = { type SapResponsePreview = {
fileUrl: string;
fileName: string; fileName: string;
rawCsv: string; downloadUrl?: string | null;
row: Record<string, string>; row: Record<string, string>;
meta: { meta: {
amountText: string; amountText: string;
@ -48,43 +48,14 @@ type SapResponsePreview = {
}; };
}; };
function buildAbsoluteBackendUrl(url: string): string { function mapSapResponseToPreviewRow(sap: Form16SapResponseRecord): Record<string, string> {
if (!url) return url; return {
if (/^https?:\/\//i.test(url)) return url; TRNS_UNIQ_NO: sap.trnsUniqNo || '',
const apiBase = ((import.meta as any).env?.VITE_API_BASE_URL as string | undefined) || 'http://localhost:5000/api/v1'; TDS_TRNS_ID: sap.tdsTransId || sap.claimNumber || '',
const origin = apiBase.replace(/\/api\/v1\/?$/i, ''); DOC_NO: sap.sapDocumentNumber || '',
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`; MSG_TYP: sap.msgTyp || '',
} MESSAGE: sap.message || '',
function parseSapResponseCsv(rawCsv: string): Record<string, string> | null {
const lines = rawCsv
.split(/\r?\n/)
.map((l) => l.trim())
.filter(Boolean);
if (lines.length < 2) return null;
const header = lines[0]!.split('|').map((h) => h.trim());
const isUsefulRow = (values: string[]) => {
const obj: Record<string, string> = {};
header.forEach((h, idx) => (obj[h] = (values[idx] || '').trim()));
const trns = (obj.TRNS_UNIQ_NO || '').trim();
const docNo = (obj.DOC_NO || '').trim();
const msgTyp = (obj.MSG_TYP || '').trim();
const tdsId = (obj.TDS_TRNS_ID || '').trim();
if (trns) return true;
if (tdsId && (docNo || msgTyp) && tdsId.toUpperCase() !== 'MSG_TYP' && tdsId.toUpperCase() !== 'MESSAGE') return true;
return false;
}; };
for (let i = lines.length - 1; i >= 1; i--) {
const values = lines[i]!.split('|');
if (isUsefulRow(values)) {
const obj: Record<string, string> = {};
header.forEach((h, idx) => (obj[h] = (values[idx] || '').trim()));
return obj;
}
}
return null;
} }
function formatIssuedAt(value: string | null | undefined): string { function formatIssuedAt(value: string | null | undefined): string {
@ -133,22 +104,16 @@ export function Form16CreditNotes() {
setPreviewLoading(true); setPreviewLoading(true);
setPreview(null); setPreview(null);
try { try {
const url = await getCreditNoteDownloadUrl(noteId); const payload = await getCreditNoteSapResponse(noteId);
const absUrl = buildAbsoluteBackendUrl(url);
const fileName = (creditNoteNumber || `credit-note-${noteId}`).trim() + '.csv'; const fileName = (creditNoteNumber || `credit-note-${noteId}`).trim() + '.csv';
const row = mapSapResponseToPreviewRow(payload.sapResponse);
const res = await apiClient.get<Blob>(absUrl, { responseType: 'blob' });
const blob = res.data;
const rawCsv = await blob.text();
const row = parseSapResponseCsv(rawCsv);
if (!row) throw new Error('Could not parse SAP response CSV');
// Enrich with UI meta (amount/date from listing row when available) // Enrich with UI meta (amount/date from listing row when available)
const note = creditNotes.find((n) => Number(n.id) === Number(noteId)); const note = creditNotes.find((n) => Number(n.id) === Number(noteId));
const amountText = formatAmount(note?.amount ?? null); const amountText = formatAmount(note?.amount ?? null);
const issuedAtText = formatIssuedAt(note?.issueDate ?? null); const issuedAtText = formatIssuedAt(note?.issueDate ?? null);
setPreview({ fileUrl: absUrl, fileName, rawCsv, row, meta: { amountText, issuedAtText } }); setPreview({ fileName, row, meta: { amountText, issuedAtText }, downloadUrl: payload.url || null });
} catch (e: any) { } catch (e: any) {
const msg = const msg =
e?.response?.status === 409 e?.response?.status === 409
@ -162,16 +127,11 @@ export function Form16CreditNotes() {
}, [creditNotes]); }, [creditNotes]);
const downloadPreviewCsv = useCallback(async () => { const downloadPreviewCsv = useCallback(async () => {
if (!preview) return; if (!preview?.downloadUrl) {
const blob = new Blob([preview.rawCsv], { type: 'text/csv;charset=utf-8' }); toast.error('CSV download link not available');
const href = URL.createObjectURL(blob); return;
const a = document.createElement('a'); }
a.href = href; window.open(preview.downloadUrl, '_blank');
a.download = preview.fileName;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(href);
}, [preview]); }, [preview]);
const filteredNotes = useMemo(() => { const filteredNotes = useMemo(() => {
@ -273,7 +233,7 @@ export function Form16CreditNotes() {
<Button variant="outline" onClick={() => setPreviewOpen(false)}> <Button variant="outline" onClick={() => setPreviewOpen(false)}>
Close Close
</Button> </Button>
<Button onClick={downloadPreviewCsv} disabled={!preview || previewLoading}> <Button onClick={downloadPreviewCsv} disabled={!preview || previewLoading || !preview?.downloadUrl}>
<Download className="w-4 h-4 mr-2" /> <Download className="w-4 h-4 mr-2" />
Download CSV Download CSV
</Button> </Button>

View File

@ -9,8 +9,9 @@ import { Search, Loader2, Receipt, RefreshCw, Eye, FileText, IndianRupee, Calend
import { toast } from 'sonner'; import { toast } from 'sonner';
import { import {
listDebitNotes, listDebitNotes,
getDebitNoteSapResponseUrl, getDebitNoteSapResponse,
type Form16DebitNoteListItem, type Form16DebitNoteListItem,
type Form16SapResponseRecord,
type ListDebitNotesParams, type ListDebitNotesParams,
type ListDebitNotesSummary, type ListDebitNotesSummary,
} from '@/services/form16Api'; } from '@/services/form16Api';
@ -43,34 +44,14 @@ function formatIssuedAt(value: string | null | undefined): string {
} }
} }
function buildAbsoluteBackendUrl(url: string): string { function mapSapResponseToPreviewRow(sap: Form16SapResponseRecord): Record<string, string> {
if (!url) return url; return {
if (/^https?:\/\//i.test(url)) return url; TRNS_UNIQ_NO: sap.trnsUniqNo || '',
const apiBase = ((import.meta as any).env?.VITE_API_BASE_URL as string | undefined) || 'http://localhost:5000/api/v1'; TDS_TRNS_ID: sap.tdsTransId || sap.claimNumber || '',
const origin = apiBase.replace(/\/api\/v1\/?$/i, ''); DOC_NO: sap.sapDocumentNumber || '',
return `${origin}${url.startsWith('/') ? '' : '/'}${url}`; MSG_TYP: sap.msgTyp || '',
} MESSAGE: sap.message || '',
};
function parseSapResponseCsv(rawCsv: string): Record<string, string> | null {
const lines = rawCsv
.split(/\r?\n/)
.map((l) => l.trim())
.filter(Boolean);
if (lines.length < 2) return null;
const header = lines[0]!.split('|').map((h) => h.trim());
// Debit response should contain: TRNS_UNIQ_NO, CLAIM_NUMBER, DOC_NO, MSG_TYP, MESSAGE
for (let i = lines.length - 1; i >= 1; i--) {
const values = lines[i]!.split('|');
const obj: Record<string, string> = {};
header.forEach((h, idx) => (obj[h] = (values[idx] || '').trim()));
const trns = (obj.TRNS_UNIQ_NO || '').trim();
const claim = (obj.CLAIM_NUMBER || '').trim();
const doc = (obj.DOC_NO || '').trim();
const typ = (obj.MSG_TYP || '').trim();
if (trns || (claim && (doc || typ))) return obj;
}
return null;
} }
const DEFAULT_SUMMARY: ListDebitNotesSummary = { const DEFAULT_SUMMARY: ListDebitNotesSummary = {
@ -129,13 +110,8 @@ export function Form16DebitNotes() {
setPreviewRow(null); setPreviewRow(null);
setPreviewMeta({ amountText: formatAmount(note.amount), issuedAtText: formatIssuedAt(note.issueDate) }); setPreviewMeta({ amountText: formatAmount(note.amount), issuedAtText: formatIssuedAt(note.issueDate) });
try { try {
const url = await getDebitNoteSapResponseUrl(Number(note.id)); const payload = await getDebitNoteSapResponse(Number(note.id));
const absUrl = buildAbsoluteBackendUrl(url); setPreviewRow(mapSapResponseToPreviewRow(payload.sapResponse));
const res = await apiClient.get<Blob>(absUrl, { responseType: 'blob' });
const rawCsv = await res.data.text();
const row = parseSapResponseCsv(rawCsv);
if (!row) throw new Error('Could not parse SAP response CSV');
setPreviewRow(row);
} catch (e: any) { } catch (e: any) {
const msg = const msg =
e?.response?.status === 409 e?.response?.status === 409
@ -195,7 +171,7 @@ export function Form16DebitNotes() {
</div> </div>
) : previewRow ? ( ) : previewRow ? (
<div className="space-y-4"> <div className="space-y-4">
{/* Block 1 (green): TRNS_UNIQ_NO + CLAIM_NUMBER */} {/* Block 1 (green): TRNS_UNIQ_NO + TDS_TRNS_ID */}
<div className="rounded-xl border border-emerald-200 bg-emerald-50/70 p-4"> <div className="rounded-xl border border-emerald-200 bg-emerald-50/70 p-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FileText className="w-4 h-4 text-emerald-700" /> <FileText className="w-4 h-4 text-emerald-700" />
@ -207,8 +183,8 @@ export function Form16DebitNotes() {
<p className="text-sm font-semibold text-emerald-900 break-all mt-1">{previewRow['TRNS_UNIQ_NO'] || ''}</p> <p className="text-sm font-semibold text-emerald-900 break-all mt-1">{previewRow['TRNS_UNIQ_NO'] || ''}</p>
</div> </div>
<div className="rounded-lg border border-emerald-200 bg-white/70 p-3"> <div className="rounded-lg border border-emerald-200 bg-white/70 p-3">
<p className="text-xs text-emerald-700 uppercase tracking-wide">CLAIM_NUMBER</p> <p className="text-xs text-emerald-700 uppercase tracking-wide">TDS_TRNS_ID</p>
<p className="text-sm font-semibold text-emerald-900 break-all mt-1">{previewRow['CLAIM_NUMBER'] || ''}</p> <p className="text-sm font-semibold text-emerald-900 break-all mt-1">{previewRow['TDS_TRNS_ID'] || ''}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -124,6 +124,26 @@ export interface ListDebitNotesParams {
quarter?: string; quarter?: string;
} }
export interface Form16SapResponseRecord {
fileName: string | null;
trnsUniqNo: string | null;
tdsTransId: string | null;
claimNumber: string | null;
sapDocumentNumber: string | null;
msgTyp: string | null;
message: string | null;
docDate: string | null;
tdsAmt: string | null;
storageUrl: string | null;
createdAt: string | null;
updatedAt: string | null;
}
export interface Form16SapResponsePayload {
sapResponse: Form16SapResponseRecord;
url?: string | null;
}
export interface Form16Permissions { export interface Form16Permissions {
canViewForm16Submission: boolean; canViewForm16Submission: boolean;
canView26AS: boolean; canView26AS: boolean;
@ -186,6 +206,20 @@ export async function getDebitNoteSapResponseUrl(id: number): Promise<string> {
return url; return url;
} }
export async function getDebitNoteSapResponse(id: number): Promise<Form16SapResponsePayload> {
const { data } = await apiClient.get<{ data?: Form16SapResponsePayload } | Form16SapResponsePayload>(`/form16/debit-notes/${id}/sap-response`);
const payload = (data && typeof data === 'object' && 'data' in data ? data.data : data) as Form16SapResponsePayload | undefined;
if (!payload?.sapResponse) throw new Error('SAP response not available');
return payload;
}
export async function getCreditNoteSapResponse(id: number): Promise<Form16SapResponsePayload> {
const { data } = await apiClient.get<{ data?: Form16SapResponsePayload } | Form16SapResponsePayload>(`/form16/credit-notes/${id}/sap-response`);
const payload = (data && typeof data === 'object' && 'data' in data ? data.data : data) as Form16SapResponsePayload | undefined;
if (!payload?.sapResponse) throw new Error('SAP response not available');
return payload;
}
/** Get credit note linked to a Form 16 request (for workflow tab). */ /** Get credit note linked to a Form 16 request (for workflow tab). */
export async function getCreditNoteByRequestId(requestId: string): Promise<Form16CreditNoteItem | null> { export async function getCreditNoteByRequestId(requestId: string): Promise<Form16CreditNoteItem | null> {
const { data } = await apiClient.get<{ data?: { creditNote?: Form16CreditNoteItem | null }; creditNote?: Form16CreditNoteItem | null }>(`/form16/requests/${encodeURIComponent(requestId)}/credit-note`); const { data } = await apiClient.get<{ data?: { creditNote?: Form16CreditNoteItem | null }; creditNote?: Form16CreditNoteItem | null }>(`/form16/requests/${encodeURIComponent(requestId)}/credit-note`);