added hsn validation and removed quatity part from the cost related items
This commit is contained in:
parent
5e91b85854
commit
5dce660f05
@ -906,7 +906,8 @@ export function DealerClaimWorkflowTab({
|
||||
cessAmt: item.cessAmt,
|
||||
totalAmt: item.totalAmt,
|
||||
quantity: item.quantity,
|
||||
hsnCode: item.hsnCode
|
||||
hsnCode: item.hsnCode,
|
||||
isService: item.isService
|
||||
})),
|
||||
totalEstimatedBudget: totalBudget,
|
||||
expectedCompletionDate: data.expectedCompletionDate,
|
||||
@ -1179,7 +1180,8 @@ export function DealerClaimWorkflowTab({
|
||||
cessAmt: item.cessAmt,
|
||||
totalAmt: item.totalAmt,
|
||||
quantity: item.quantity,
|
||||
hsnCode: item.hsnCode
|
||||
hsnCode: item.hsnCode,
|
||||
isService: item.isService
|
||||
}));
|
||||
|
||||
// Submit completion documents using dealer claim API
|
||||
@ -2373,6 +2375,7 @@ export function DealerClaimWorkflowTab({
|
||||
onSubmit={handleProposalSubmit}
|
||||
dealerName={dealerName}
|
||||
activityName={activityName}
|
||||
defaultGstRate={request?.claimDetails?.defaultGstRate}
|
||||
requestId={request?.id || request?.requestId}
|
||||
previousProposalData={versionHistory?.find(v => v.snapshotType === 'PROPOSAL')?.snapshotData}
|
||||
documentPolicy={documentPolicy}
|
||||
@ -2421,6 +2424,7 @@ export function DealerClaimWorkflowTab({
|
||||
onSubmit={handleCompletionSubmit}
|
||||
dealerName={dealerName}
|
||||
activityName={activityName}
|
||||
defaultGstRate={request?.claimDetails?.defaultGstRate}
|
||||
requestId={request?.id || request?.requestId}
|
||||
documentPolicy={documentPolicy}
|
||||
/>
|
||||
|
||||
@ -109,7 +109,7 @@ export function ActivityInformationCard({
|
||||
</label>
|
||||
<p className="text-sm text-gray-900 font-medium mt-1 flex items-center gap-2">
|
||||
<DollarSign className="w-4 h-4 text-green-600" />
|
||||
{activityInfo.estimatedBudget
|
||||
{activityInfo.estimatedBudget !== undefined && activityInfo.estimatedBudget !== null
|
||||
? formatCurrency(activityInfo.estimatedBudget)
|
||||
: 'TBD'}
|
||||
</p>
|
||||
|
||||
@ -23,6 +23,7 @@ import { Upload, Plus, X, Calendar, FileText, Image, Receipt, CircleAlert, Check
|
||||
import { toast } from 'sonner';
|
||||
import '@/components/common/FilePreview/FilePreview.css';
|
||||
import './DealerCompletionDocumentsModal.css';
|
||||
import { validateHSNSAC } from '@/utils/validationUtils';
|
||||
|
||||
interface ExpenseItem {
|
||||
id: string;
|
||||
@ -43,6 +44,7 @@ interface ExpenseItem {
|
||||
cessRate: number;
|
||||
cessAmt: number;
|
||||
totalAmt: number;
|
||||
isService: boolean;
|
||||
}
|
||||
|
||||
interface DealerCompletionDocumentsModalProps {
|
||||
@ -62,6 +64,7 @@ interface DealerCompletionDocumentsModalProps {
|
||||
dealerName?: string;
|
||||
activityName?: string;
|
||||
requestId?: string;
|
||||
defaultGstRate?: number;
|
||||
documentPolicy: {
|
||||
maxFileSizeMB: number;
|
||||
allowedFileTypes: string[];
|
||||
@ -75,6 +78,7 @@ export function DealerCompletionDocumentsModal({
|
||||
dealerName = 'Jaipur Royal Enfield',
|
||||
activityName = 'Activity',
|
||||
requestId: _requestId,
|
||||
defaultGstRate = 18,
|
||||
documentPolicy,
|
||||
}: DealerCompletionDocumentsModalProps) {
|
||||
const [activityCompletionDate, setActivityCompletionDate] = useState('');
|
||||
@ -116,6 +120,33 @@ export function DealerCompletionDocumentsModal({
|
||||
};
|
||||
}, [previewFile]);
|
||||
|
||||
// Initialize with one empty row if none exist
|
||||
useEffect(() => {
|
||||
if (expenseItems.length === 0) {
|
||||
setExpenseItems([{
|
||||
id: '1',
|
||||
description: '',
|
||||
amount: 0,
|
||||
gstRate: defaultGstRate || 0,
|
||||
gstAmt: 0,
|
||||
quantity: 1,
|
||||
hsnCode: '',
|
||||
isService: false,
|
||||
cgstRate: 0,
|
||||
cgstAmt: 0,
|
||||
sgstRate: 0,
|
||||
sgstAmt: 0,
|
||||
igstRate: 0,
|
||||
igstAmt: 0,
|
||||
utgstRate: 0,
|
||||
utgstAmt: 0,
|
||||
cessRate: 0,
|
||||
cessAmt: 0,
|
||||
totalAmt: 0
|
||||
}]);
|
||||
}
|
||||
}, [defaultGstRate]);
|
||||
|
||||
// Handle file preview
|
||||
const handlePreviewFile = (file: File) => {
|
||||
if (!canPreviewFile(file)) {
|
||||
@ -189,7 +220,12 @@ export function DealerCompletionDocumentsModal({
|
||||
const hasPhotos = activityPhotos.length > 0;
|
||||
const hasDescription = completionDescription.trim().length > 0;
|
||||
|
||||
return hasCompletionDate && hasDocuments && hasPhotos && hasDescription;
|
||||
const hasHSNSACErrors = expenseItems.some(item => {
|
||||
const { isValid } = validateHSNSAC(item.hsnCode, item.isService);
|
||||
return !isValid;
|
||||
});
|
||||
|
||||
return hasCompletionDate && hasDocuments && hasPhotos && hasDescription && !hasHSNSACErrors;
|
||||
}, [activityCompletionDate, completionDocuments, activityPhotos, completionDescription]);
|
||||
|
||||
// Get today's date in YYYY-MM-DD format for max date
|
||||
@ -202,10 +238,11 @@ export function DealerCompletionDocumentsModal({
|
||||
id: Date.now().toString(),
|
||||
description: '',
|
||||
amount: 0,
|
||||
gstRate: 0,
|
||||
gstRate: defaultGstRate || 0,
|
||||
gstAmt: 0,
|
||||
quantity: 1,
|
||||
hsnCode: '',
|
||||
isService: false,
|
||||
cgstRate: 0,
|
||||
cgstAmt: 0,
|
||||
sgstRate: 0,
|
||||
@ -227,11 +264,11 @@ export function DealerCompletionDocumentsModal({
|
||||
if (item.id === id) {
|
||||
const updatedItem = { ...item, [field]: value };
|
||||
|
||||
// Re-calculate GST if amount, rate or quantity changes
|
||||
if (field === 'amount' || field === 'gstRate' || field === 'quantity') {
|
||||
// Re-calculate GST if amount or rate changes
|
||||
if (field === 'amount' || field === 'gstRate') {
|
||||
const amount = field === 'amount' ? parseFloat(value) || 0 : item.amount;
|
||||
const rate = field === 'gstRate' ? parseFloat(value) || 0 : item.gstRate;
|
||||
const quantity = field === 'quantity' ? parseInt(value) || 1 : item.quantity;
|
||||
const quantity = 1;
|
||||
const gst = calculateGST(amount, rate, quantity);
|
||||
|
||||
return {
|
||||
@ -428,6 +465,21 @@ export function DealerCompletionDocumentsModal({
|
||||
(item) => item.description.trim() !== '' && item.amount > 0
|
||||
);
|
||||
|
||||
// Validation: Alert for 0% GST on taxable items
|
||||
const hasZeroGstItems = validExpenses.some(item =>
|
||||
item.description.trim() !== '' && item.amount > 0 && (item.gstRate === 0 || !item.gstRate)
|
||||
);
|
||||
|
||||
if (hasZeroGstItems) {
|
||||
const confirmZeroGst = window.confirm(
|
||||
"One or more expenses have 0% GST. Are you sure you want to proceed? \n\nNote: If these items are taxable, please provide a valid GST rate to ensure correct E-Invoice generation."
|
||||
);
|
||||
if (!confirmZeroGst) {
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
setSubmitting(true);
|
||||
await onSubmit({
|
||||
@ -463,10 +515,11 @@ export function DealerCompletionDocumentsModal({
|
||||
id: '1',
|
||||
description: '',
|
||||
amount: 0,
|
||||
gstRate: 0,
|
||||
gstRate: defaultGstRate || 0,
|
||||
gstAmt: 0,
|
||||
quantity: 1,
|
||||
hsnCode: '',
|
||||
isService: false,
|
||||
cgstRate: 0,
|
||||
cgstAmt: 0,
|
||||
sgstRate: 0,
|
||||
@ -597,20 +650,26 @@ export function DealerCompletionDocumentsModal({
|
||||
onChange={(e) =>
|
||||
handleExpenseChange(item.id, 'hsnCode', e.target.value)
|
||||
}
|
||||
className="w-full bg-white text-sm"
|
||||
className={`w-full bg-white text-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
|
||||
/>
|
||||
{!validateHSNSAC(item.hsnCode, item.isService).isValid && (
|
||||
<span className="text-[9px] text-red-500 mt-1 block leading-tight">
|
||||
{validateHSNSAC(item.hsnCode, item.isService).message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-16 sm:w-20 flex-shrink-0">
|
||||
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Qty</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
value={item.quantity || 1}
|
||||
<div className="w-24 sm:w-28 flex-shrink-0">
|
||||
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item Type</Label>
|
||||
<select
|
||||
value={item.isService ? 'SAC' : 'HSN'}
|
||||
onChange={(e) =>
|
||||
handleExpenseChange(item.id, 'quantity', parseInt(e.target.value) || 1)
|
||||
handleExpenseChange(item.id, 'isService', e.target.value === 'SAC')
|
||||
}
|
||||
className="w-full bg-white text-sm"
|
||||
/>
|
||||
className="flex h-9 w-full rounded-md border border-input bg-white px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="HSN">HSN (Goods)</option>
|
||||
<option value="SAC">SAC (Service)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="w-20 sm:w-24 flex-shrink-0">
|
||||
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">GST %</Label>
|
||||
|
||||
@ -26,6 +26,7 @@ import { FilePreview } from '@/components/common/FilePreview/FilePreview';
|
||||
import { getDocumentPreviewUrl, downloadDocument } from '@/services/workflowApi';
|
||||
import '@/components/common/FilePreview/FilePreview.css';
|
||||
import './DealerProposalModal.css';
|
||||
import { validateHSNSAC } from '@/utils/validationUtils';
|
||||
|
||||
interface CostItem {
|
||||
id: string;
|
||||
@ -46,6 +47,7 @@ interface CostItem {
|
||||
cessRate: number;
|
||||
cessAmt: number;
|
||||
totalAmt: number;
|
||||
isService: boolean;
|
||||
}
|
||||
|
||||
interface DealerProposalSubmissionModalProps {
|
||||
@ -62,6 +64,7 @@ interface DealerProposalSubmissionModalProps {
|
||||
activityName?: string;
|
||||
requestId?: string;
|
||||
previousProposalData?: any;
|
||||
defaultGstRate?: number;
|
||||
documentPolicy: {
|
||||
maxFileSizeMB: number;
|
||||
allowedFileTypes: string[];
|
||||
@ -76,6 +79,7 @@ export function DealerProposalSubmissionModal({
|
||||
activityName = 'Activity',
|
||||
requestId: _requestId,
|
||||
previousProposalData,
|
||||
defaultGstRate = 18,
|
||||
documentPolicy,
|
||||
}: DealerProposalSubmissionModalProps) {
|
||||
const [proposalDocument, setProposalDocument] = useState<File | null>(null);
|
||||
@ -84,10 +88,11 @@ export function DealerProposalSubmissionModal({
|
||||
id: '1',
|
||||
description: '',
|
||||
amount: 0,
|
||||
gstRate: 0,
|
||||
gstRate: defaultGstRate || 0,
|
||||
gstAmt: 0,
|
||||
quantity: 1,
|
||||
hsnCode: '',
|
||||
isService: false,
|
||||
cgstRate: 0,
|
||||
cgstAmt: 0,
|
||||
sgstRate: 0,
|
||||
@ -248,9 +253,14 @@ export function DealerProposalSubmissionModal({
|
||||
const hasTimeline = timelineMode === 'date'
|
||||
? expectedCompletionDate !== ''
|
||||
: numberOfDays !== '' && parseInt(numberOfDays) > 0;
|
||||
const hasComments = dealerComments.trim().length > 0;
|
||||
const hasValidComments = dealerComments.trim().length > 0;
|
||||
|
||||
return hasProposalDoc && hasValidCostItems && hasTimeline && hasComments;
|
||||
const hasHSNSACErrors = costItems.some(item => {
|
||||
const { isValid } = validateHSNSAC(item.hsnCode, item.isService);
|
||||
return !isValid;
|
||||
});
|
||||
|
||||
return hasProposalDoc && hasValidCostItems && hasTimeline && hasValidComments && !hasHSNSACErrors;
|
||||
}, [proposalDocument, costItems, timelineMode, expectedCompletionDate, numberOfDays, dealerComments]);
|
||||
|
||||
const handleProposalDocChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -316,10 +326,11 @@ export function DealerProposalSubmissionModal({
|
||||
id: Date.now().toString(),
|
||||
description: '',
|
||||
amount: 0,
|
||||
gstRate: 0,
|
||||
gstRate: defaultGstRate || 0,
|
||||
gstAmt: 0,
|
||||
quantity: 1,
|
||||
hsnCode: '',
|
||||
isService: false,
|
||||
cgstRate: 0,
|
||||
cgstAmt: 0,
|
||||
sgstRate: 0,
|
||||
@ -347,11 +358,11 @@ export function DealerProposalSubmissionModal({
|
||||
if (item.id === id) {
|
||||
const updatedItem = { ...item, [field]: value };
|
||||
|
||||
// Re-calculate GST if amount, rate or quantity changes
|
||||
if (field === 'amount' || field === 'gstRate' || field === 'quantity') {
|
||||
// Re-calculate GST if amount or rate changes
|
||||
if (field === 'amount' || field === 'gstRate') {
|
||||
const amount = field === 'amount' ? parseFloat(value) || 0 : item.amount;
|
||||
const rate = field === 'gstRate' ? parseFloat(value) || 0 : item.gstRate;
|
||||
const quantity = field === 'quantity' ? parseInt(value) || 1 : item.quantity;
|
||||
const quantity = 1;
|
||||
const gst = calculateGST(amount, rate, quantity);
|
||||
|
||||
return {
|
||||
@ -392,6 +403,21 @@ export function DealerProposalSubmissionModal({
|
||||
|
||||
try {
|
||||
setSubmitting(true);
|
||||
// Validation: Alert for 0% GST on taxable items
|
||||
const hasZeroGstItems = costItems.some(item =>
|
||||
item.description.trim() !== '' && item.amount > 0 && (item.gstRate === 0 || !item.gstRate)
|
||||
);
|
||||
|
||||
if (hasZeroGstItems) {
|
||||
const confirmZeroGst = window.confirm(
|
||||
"One or more items have 0% GST. Are you sure you want to proceed? \n\nNote: If these items are taxable, please provide a valid GST rate to ensure correct E-Invoice generation."
|
||||
);
|
||||
if (!confirmZeroGst) {
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await onSubmit({
|
||||
proposalDocument,
|
||||
costBreakup: costItems.filter(item => item.description.trim() !== '' && item.amount > 0),
|
||||
@ -424,6 +450,7 @@ export function DealerProposalSubmissionModal({
|
||||
gstAmt: 0,
|
||||
quantity: 1,
|
||||
hsnCode: '',
|
||||
isService: false,
|
||||
cgstRate: 0,
|
||||
cgstAmt: 0,
|
||||
sgstRate: 0,
|
||||
@ -906,20 +933,26 @@ export function DealerProposalSubmissionModal({
|
||||
onChange={(e) =>
|
||||
handleCostItemChange(item.id, 'hsnCode', e.target.value)
|
||||
}
|
||||
className="w-full bg-white"
|
||||
className={`w-full bg-white ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
|
||||
/>
|
||||
{!validateHSNSAC(item.hsnCode, item.isService).isValid && (
|
||||
<span className="text-[9px] text-red-500 mt-1 block leading-tight">
|
||||
{validateHSNSAC(item.hsnCode, item.isService).message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Qty</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
value={item.quantity || 1}
|
||||
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item Type</Label>
|
||||
<select
|
||||
value={item.isService ? 'SAC' : 'HSN'}
|
||||
onChange={(e) =>
|
||||
handleCostItemChange(item.id, 'quantity', parseInt(e.target.value) || 1)
|
||||
handleCostItemChange(item.id, 'isService', e.target.value === 'SAC')
|
||||
}
|
||||
className="w-full bg-white"
|
||||
/>
|
||||
className="flex h-10 w-full rounded-md border border-input bg-white px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="HSN">HSN (Goods)</option>
|
||||
<option value="SAC">SAC (Serv.)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">GST %</Label>
|
||||
|
||||
@ -78,7 +78,22 @@ export async function submitProposal(
|
||||
requestId: string,
|
||||
proposalData: {
|
||||
proposalDocument?: File;
|
||||
costBreakup?: Array<{ description: string; amount: number }>;
|
||||
costBreakup?: Array<{
|
||||
description: string;
|
||||
amount: number;
|
||||
hsnCode?: string;
|
||||
isService?: boolean;
|
||||
quantity?: number;
|
||||
gstRate?: number;
|
||||
gstAmt?: number;
|
||||
cgstRate?: number;
|
||||
cgstAmt?: number;
|
||||
sgstRate?: number;
|
||||
sgstAmt?: number;
|
||||
igstRate?: number;
|
||||
igstAmt?: number;
|
||||
totalAmt?: number;
|
||||
}>;
|
||||
totalEstimatedBudget?: number;
|
||||
timelineMode?: 'date' | 'days';
|
||||
expectedCompletionDate?: string;
|
||||
@ -139,7 +154,16 @@ export async function submitCompletion(
|
||||
completionData: {
|
||||
activityCompletionDate: string; // ISO date string
|
||||
numberOfParticipants?: number;
|
||||
closedExpenses?: Array<{ description: string; amount: number }>;
|
||||
closedExpenses?: Array<{
|
||||
description: string;
|
||||
amount: number;
|
||||
hsnCode?: string;
|
||||
isService?: boolean;
|
||||
quantity?: number;
|
||||
gstRate?: number;
|
||||
gstAmt?: number;
|
||||
totalAmt?: number;
|
||||
}>;
|
||||
totalClosedExpenses?: number;
|
||||
completionDocuments?: File[];
|
||||
activityPhotos?: File[];
|
||||
|
||||
@ -26,6 +26,7 @@ export interface ClaimManagementRequest {
|
||||
};
|
||||
estimatedBudget?: number;
|
||||
closedExpenses?: number;
|
||||
defaultGstRate?: number;
|
||||
closedExpensesBreakdown?: Array<{
|
||||
description: string;
|
||||
amount: number;
|
||||
@ -153,19 +154,19 @@ export function mapToClaimManagementRequest(
|
||||
// Activity fields mapped
|
||||
|
||||
// Get budget values from budgetTracking table (new source of truth)
|
||||
const estimatedBudget = budgetTracking.proposalEstimatedBudget ||
|
||||
budgetTracking.proposal_estimated_budget ||
|
||||
budgetTracking.initialEstimatedBudget ||
|
||||
budgetTracking.initial_estimated_budget ||
|
||||
claimDetails.estimatedBudget ||
|
||||
const estimatedBudget = budgetTracking.proposalEstimatedBudget ??
|
||||
budgetTracking.proposal_estimated_budget ??
|
||||
budgetTracking.initialEstimatedBudget ??
|
||||
budgetTracking.initial_estimated_budget ??
|
||||
claimDetails.estimatedBudget ??
|
||||
claimDetails.estimated_budget;
|
||||
|
||||
// Get closed expenses - check multiple sources with proper number conversion
|
||||
const closedExpensesRaw = budgetTracking?.closedExpenses ||
|
||||
budgetTracking?.closed_expenses ||
|
||||
completionDetails?.totalClosedExpenses ||
|
||||
completionDetails?.total_closed_expenses ||
|
||||
claimDetails?.closedExpenses ||
|
||||
const closedExpensesRaw = budgetTracking?.closedExpenses ??
|
||||
budgetTracking?.closed_expenses ??
|
||||
completionDetails?.totalClosedExpenses ??
|
||||
completionDetails?.total_closed_expenses ??
|
||||
claimDetails?.closedExpenses ??
|
||||
claimDetails?.closed_expenses;
|
||||
// Convert to number and handle 0 as valid value
|
||||
const closedExpenses = closedExpensesRaw !== null && closedExpensesRaw !== undefined
|
||||
@ -192,6 +193,7 @@ export function mapToClaimManagementRequest(
|
||||
const activityInfo = {
|
||||
activityName,
|
||||
activityType,
|
||||
defaultGstRate: claimDetails.defaultGstRate || 18,
|
||||
requestedDate: claimDetails.activityDate || claimDetails.activity_date || apiRequest.createdAt, // Use activityDate as requestedDate, fallback to createdAt
|
||||
location,
|
||||
period: (periodStartDate && periodEndDate) ? {
|
||||
|
||||
50
src/utils/validationUtils.ts
Normal file
50
src/utils/validationUtils.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Validation utilities for HSN and SAC codes
|
||||
*/
|
||||
|
||||
export interface ValidationResult {
|
||||
isValid: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates HSN or SAC code based on GST rules
|
||||
* @param code The HSN/SAC code string
|
||||
* @param isService Boolean indicating if it's a Service (SAC) or Goods (HSN)
|
||||
* @returns ValidationResult object
|
||||
*/
|
||||
export const validateHSNSAC = (code: string, isService: boolean): ValidationResult => {
|
||||
if (!code) return { isValid: true, message: '' };
|
||||
|
||||
const cleanCode = code.trim();
|
||||
|
||||
// Basic check for digits only
|
||||
if (!/^\d+$/.test(cleanCode)) {
|
||||
return { isValid: false, message: 'Code must contain only digits' };
|
||||
}
|
||||
|
||||
if (isService) {
|
||||
// SAC (Services Accounting Code)
|
||||
// Must start with 99 and typically has 6 digits
|
||||
if (!cleanCode.startsWith('99')) {
|
||||
return { isValid: false, message: 'SAC (Service) code must start with 99' };
|
||||
}
|
||||
if (cleanCode.length !== 6) {
|
||||
return { isValid: false, message: 'SAC code must be exactly 6 digits' };
|
||||
}
|
||||
} else {
|
||||
// HSN (Harmonized System of Nomenclature) for Goods
|
||||
// Usually 4, 6, or 8 digits in India
|
||||
const validHSNLengths = [4, 6, 8];
|
||||
if (!validHSNLengths.includes(cleanCode.length)) {
|
||||
return { isValid: false, message: 'HSN code must be 4, 6, or 8 digits' };
|
||||
}
|
||||
|
||||
// HSN codes for goods should generally not start with 99 (that's reserved for SAC)
|
||||
if (cleanCode.startsWith('99')) {
|
||||
return { isValid: false, message: 'HSN code should not start with 99 (use SAC type for services)' };
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true, message: '' };
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user