- |
+ |
Estimated Budget (Total Inclusive of GST)
|
diff --git a/src/dealer-claim/components/request-detail/modals/DealerCompletionDocumentsModal.tsx b/src/dealer-claim/components/request-detail/modals/DealerCompletionDocumentsModal.tsx
index 0d24b34..dcaa94f 100644
--- a/src/dealer-claim/components/request-detail/modals/DealerCompletionDocumentsModal.tsx
+++ b/src/dealer-claim/components/request-detail/modals/DealerCompletionDocumentsModal.tsx
@@ -24,6 +24,7 @@ import { toast } from 'sonner';
import '@/components/common/FilePreview/FilePreview.css';
import './DealerCompletionDocumentsModal.css';
import { validateHSNSAC } from '@/utils/validationUtils';
+import { getStateCodeFromGSTIN, getActiveTaxComponents } from '@/utils/gstUtils';
interface ExpenseItem {
id: string;
@@ -62,6 +63,7 @@ interface DealerCompletionDocumentsModalProps {
completionDescription: string;
}) => Promise;
dealerName?: string;
+ dealerGSTIN?: string;
activityName?: string;
requestId?: string;
defaultGstRate?: number;
@@ -76,6 +78,7 @@ export function DealerCompletionDocumentsModal({
onClose,
onSubmit,
dealerName = 'Jaipur Royal Enfield',
+ dealerGSTIN,
activityName = 'Activity',
requestId: _requestId,
defaultGstRate = 18,
@@ -83,6 +86,13 @@ export function DealerCompletionDocumentsModal({
}: DealerCompletionDocumentsModalProps) {
const [activityCompletionDate, setActivityCompletionDate] = useState('');
const [numberOfParticipants, setNumberOfParticipants] = useState('');
+
+ // Determine active tax components based on dealer GSTIN
+ const taxConfig = useMemo(() => {
+ const stateCode = getStateCodeFromGSTIN(dealerGSTIN);
+ return getActiveTaxComponents(stateCode);
+ }, [dealerGSTIN]);
+
const [expenseItems, setExpenseItems] = useState([]);
const [completionDocuments, setCompletionDocuments] = useState([]);
const [activityPhotos, setActivityPhotos] = useState([]);
@@ -181,36 +191,28 @@ export function DealerCompletionDocumentsModal({
}, [expenseItems]);
// GST Calculation Helper
- const calculateGST = (amount: number, rate: number, quantity: number = 1, type: 'IGST' | 'CGST_SGST' = 'CGST_SGST') => {
+ const calculateGST = (amount: number, rates: { cgstRate: number; sgstRate: number; igstRate: number; utgstRate: number }, quantity: number = 1) => {
const baseTotal = amount * quantity;
- const gstAmt = (baseTotal * rate) / 100;
+ const cgstAmt = (baseTotal * (rates.cgstRate || 0)) / 100;
+ const sgstAmt = (baseTotal * (rates.sgstRate || 0)) / 100;
+ const utgstAmt = (baseTotal * (rates.utgstRate || 0)) / 100;
+ const igstAmt = (baseTotal * (rates.igstRate || 0)) / 100;
+ const gstAmt = cgstAmt + sgstAmt + utgstAmt + igstAmt;
const totalAmt = baseTotal + gstAmt;
- if (type === 'IGST') {
- return {
- gstAmt,
- igstRate: rate,
- igstAmt: gstAmt,
- cgstRate: 0,
- cgstAmt: 0,
- sgstRate: 0,
- sgstAmt: 0,
- totalAmt
- };
- } else {
- const halfRate = rate / 2;
- const halfAmt = gstAmt / 2;
- return {
- gstAmt,
- igstRate: 0,
- igstAmt: 0,
- cgstRate: halfRate,
- cgstAmt: halfAmt,
- sgstRate: halfRate,
- sgstAmt: halfAmt,
- totalAmt
- };
- }
+ return {
+ cgstRate: rates.cgstRate,
+ cgstAmt,
+ sgstRate: rates.sgstRate,
+ sgstAmt,
+ utgstRate: rates.utgstRate,
+ utgstAmt,
+ igstRate: rates.igstRate,
+ igstAmt,
+ gstAmt,
+ gstRate: (rates.cgstRate || 0) + (rates.sgstRate || 0) + (rates.utgstRate || 0) + (rates.igstRate || 0),
+ totalAmt
+ };
};
// Check if all required fields are filled
@@ -262,21 +264,75 @@ export function DealerCompletionDocumentsModal({
setExpenseItems(
expenseItems.map((item) => {
if (item.id === id) {
- const updatedItem = { ...item, [field]: value };
+ let updatedItem = { ...item, [field]: value };
- // Re-calculate GST if amount or rate changes
- if (field === 'amount' || field === 'gstRate') {
+ // Re-calculate GST if relevant fields change
+ if (['amount', 'gstRate', 'cgstRate', 'sgstRate', 'utgstRate', 'igstRate', 'quantity'].includes(field)) {
const amount = field === 'amount' ? parseFloat(value) || 0 : item.amount;
- const rate = field === 'gstRate' ? parseFloat(value) || 0 : item.gstRate;
- const quantity = 1;
- const gst = calculateGST(amount, rate, quantity);
+ const quantity = field === 'quantity' ? parseInt(value) || 1 : item.quantity;
+
+ let cgstRate = item.cgstRate;
+ let sgstRate = item.sgstRate;
+ let utgstRate = item.utgstRate;
+ let igstRate = item.igstRate;
+
+ if (field === 'cgstRate') {
+ if (!taxConfig.isCGST) return item;
+ cgstRate = parseFloat(value) || 0;
+ // If UTGST is active for this dealer, sync with it
+ if (taxConfig.isUTGST) {
+ utgstRate = cgstRate;
+ sgstRate = 0;
+ } else {
+ sgstRate = cgstRate;
+ utgstRate = 0;
+ }
+ igstRate = 0;
+ } else if (field === 'sgstRate') {
+ if (!taxConfig.isSGST) return item;
+ sgstRate = parseFloat(value) || 0;
+ cgstRate = sgstRate;
+ utgstRate = 0;
+ igstRate = 0;
+ } else if (field === 'utgstRate') {
+ if (!taxConfig.isUTGST) return item;
+ utgstRate = parseFloat(value) || 0;
+ cgstRate = utgstRate;
+ sgstRate = 0;
+ igstRate = 0;
+ } else if (field === 'igstRate') {
+ if (!taxConfig.isIGST) return item;
+ igstRate = parseFloat(value) || 0;
+ cgstRate = 0;
+ sgstRate = 0;
+ utgstRate = 0;
+ } else if (field === 'gstRate') {
+ const totalRate = parseFloat(value) || 0;
+ if (taxConfig.isIGST) {
+ igstRate = totalRate;
+ cgstRate = 0;
+ sgstRate = 0;
+ utgstRate = 0;
+ } else {
+ cgstRate = totalRate / 2;
+ if (taxConfig.isUTGST) {
+ utgstRate = totalRate / 2;
+ sgstRate = 0;
+ } else {
+ sgstRate = totalRate / 2;
+ utgstRate = 0;
+ }
+ igstRate = 0;
+ }
+ }
+
+ const calculation = calculateGST(amount, { cgstRate, sgstRate, igstRate, utgstRate }, quantity);
return {
...updatedItem,
amount,
- gstRate: rate,
quantity,
- ...gst
+ ...calculation
};
}
@@ -576,7 +632,7 @@ export function DealerCompletionDocumentsModal({
-
+
{/* Activity Completion Date */}
+
+ Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state).
+
-
-
+
+
- handleExpenseChange(item.id, 'gstRate', e.target.value)
+ handleExpenseChange(item.id, 'cgstRate', e.target.value)
}
- className="w-full bg-white text-sm"
+ disabled={!taxConfig.isCGST}
+ className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400"
+ />
+
+
+
+
+ handleExpenseChange(item.id, 'sgstRate', e.target.value)
+ }
+ disabled={!taxConfig.isSGST}
+ className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400"
+ />
+
+
+
+
+ handleExpenseChange(item.id, 'utgstRate', e.target.value)
+ }
+ disabled={!taxConfig.isUTGST}
+ className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400"
+ />
+
+
+
+
+ handleExpenseChange(item.id, 'igstRate', e.target.value)
+ }
+ disabled={!taxConfig.isIGST}
+ className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400"
/>
- {item.gstAmt > 0 && (
-
-
- CGST ({item.cgstRate}%)
- ₹{item.cgstAmt.toLocaleString('en-IN')}
-
-
- SGST ({item.sgstRate}%)
- ₹{item.sgstAmt.toLocaleString('en-IN')}
-
-
- GST Total
- ₹{item.gstAmt.toLocaleString('en-IN')}
-
-
- Item Total
- ₹{item.totalAmt.toLocaleString('en-IN')}
-
+
+
+ CGST
+ ₹{item.cgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}
- )}
+ {item.sgstAmt > 0 && (
+
+ SGST
+ ₹{item.sgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}
+
+ )}
+ {item.utgstAmt > 0 && (
+
+ UTGST
+ ₹{item.utgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}
+
+ )}
+
+ IGST
+ ₹{item.igstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}
+
+
+ GST Total
+ ₹{item.gstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}
+
+
+ Item Total
+ ₹{item.totalAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}
+
+
))}
diff --git a/src/dealer-claim/components/request-detail/modals/DealerProposalSubmissionModal.tsx b/src/dealer-claim/components/request-detail/modals/DealerProposalSubmissionModal.tsx
index 27f610b..b0cca76 100644
--- a/src/dealer-claim/components/request-detail/modals/DealerProposalSubmissionModal.tsx
+++ b/src/dealer-claim/components/request-detail/modals/DealerProposalSubmissionModal.tsx
@@ -27,6 +27,7 @@ import { getDocumentPreviewUrl, downloadDocument } from '@/services/workflowApi'
import '@/components/common/FilePreview/FilePreview.css';
import './DealerProposalModal.css';
import { validateHSNSAC } from '@/utils/validationUtils';
+import { getStateCodeFromGSTIN, getActiveTaxComponents } from '@/utils/gstUtils';
interface CostItem {
id: string;
@@ -61,6 +62,7 @@ interface DealerProposalSubmissionModalProps {
dealerComments: string;
}) => Promise ;
dealerName?: string;
+ dealerGSTIN?: string;
activityName?: string;
requestId?: string;
previousProposalData?: any;
@@ -76,6 +78,7 @@ export function DealerProposalSubmissionModal({
onClose,
onSubmit,
dealerName = 'Jaipur Royal Enfield',
+ dealerGSTIN,
activityName = 'Activity',
requestId: _requestId,
previousProposalData,
@@ -83,6 +86,13 @@ export function DealerProposalSubmissionModal({
documentPolicy,
}: DealerProposalSubmissionModalProps) {
const [proposalDocument, setProposalDocument] = useState(null);
+
+ // Determine active tax components based on dealer GSTIN
+ const taxConfig = useMemo(() => {
+ const stateCode = getStateCodeFromGSTIN(dealerGSTIN);
+ return getActiveTaxComponents(stateCode);
+ }, [dealerGSTIN]);
+
const [costItems, setCostItems] = useState([
{
id: '1',
@@ -108,36 +118,28 @@ export function DealerProposalSubmissionModal({
]);
// GST Calculation Helper
- const calculateGST = (amount: number, rate: number, quantity: number = 1, type: 'IGST' | 'CGST_SGST' = 'CGST_SGST') => {
+ const calculateGST = (amount: number, rates: { cgstRate: number; sgstRate: number; igstRate: number; utgstRate: number }, quantity: number = 1) => {
const baseTotal = amount * quantity;
- const gstAmt = (baseTotal * rate) / 100;
+ const cgstAmt = (baseTotal * (rates.cgstRate || 0)) / 100;
+ const sgstAmt = (baseTotal * (rates.sgstRate || 0)) / 100;
+ const utgstAmt = (baseTotal * (rates.utgstRate || 0)) / 100;
+ const igstAmt = (baseTotal * (rates.igstRate || 0)) / 100;
+ const gstAmt = cgstAmt + sgstAmt + utgstAmt + igstAmt;
const totalAmt = baseTotal + gstAmt;
- if (type === 'IGST') {
- return {
- gstAmt,
- igstRate: rate,
- igstAmt: gstAmt,
- cgstRate: 0,
- cgstAmt: 0,
- sgstRate: 0,
- sgstAmt: 0,
- totalAmt
- };
- } else {
- const halfRate = rate / 2;
- const halfAmt = gstAmt / 2;
- return {
- gstAmt,
- igstRate: 0,
- igstAmt: 0,
- cgstRate: halfRate,
- cgstAmt: halfAmt,
- sgstRate: halfRate,
- sgstAmt: halfAmt,
- totalAmt
- };
- }
+ return {
+ cgstRate: rates.cgstRate,
+ cgstAmt,
+ sgstRate: rates.sgstRate,
+ sgstAmt,
+ utgstRate: rates.utgstRate,
+ utgstAmt,
+ igstRate: rates.igstRate,
+ igstAmt,
+ gstAmt,
+ gstRate: (rates.cgstRate || 0) + (rates.sgstRate || 0) + (rates.utgstRate || 0) + (rates.igstRate || 0),
+ totalAmt
+ };
};
const [timelineMode, setTimelineMode] = useState<'date' | 'days'>('date');
const [expectedCompletionDate, setExpectedCompletionDate] = useState('');
@@ -356,21 +358,75 @@ export function DealerProposalSubmissionModal({
setCostItems(prev =>
prev.map(item => {
if (item.id === id) {
- const updatedItem = { ...item, [field]: value };
+ let updatedItem = { ...item, [field]: value };
- // Re-calculate GST if amount or rate changes
- if (field === 'amount' || field === 'gstRate') {
+ // Re-calculate GST if relevant fields change
+ if (['amount', 'gstRate', 'cgstRate', 'sgstRate', 'utgstRate', 'igstRate', 'quantity'].includes(field)) {
const amount = field === 'amount' ? parseFloat(value) || 0 : item.amount;
- const rate = field === 'gstRate' ? parseFloat(value) || 0 : item.gstRate;
- const quantity = 1;
- const gst = calculateGST(amount, rate, quantity);
+ const quantity = field === 'quantity' ? parseInt(value) || 1 : item.quantity;
+
+ let cgstRate = item.cgstRate;
+ let sgstRate = item.sgstRate;
+ let utgstRate = item.utgstRate;
+ let igstRate = item.igstRate;
+
+ if (field === 'cgstRate') {
+ if (!taxConfig.isCGST) return item;
+ cgstRate = parseFloat(value) || 0;
+ // If UTGST is active for this dealer, sync with it
+ if (taxConfig.isUTGST) {
+ utgstRate = cgstRate;
+ sgstRate = 0;
+ } else {
+ sgstRate = cgstRate;
+ utgstRate = 0;
+ }
+ igstRate = 0;
+ } else if (field === 'sgstRate') {
+ if (!taxConfig.isSGST) return item;
+ sgstRate = parseFloat(value) || 0;
+ cgstRate = sgstRate;
+ utgstRate = 0;
+ igstRate = 0;
+ } else if (field === 'utgstRate') {
+ if (!taxConfig.isUTGST) return item;
+ utgstRate = parseFloat(value) || 0;
+ cgstRate = utgstRate;
+ sgstRate = 0;
+ igstRate = 0;
+ } else if (field === 'igstRate') {
+ if (!taxConfig.isIGST) return item;
+ igstRate = parseFloat(value) || 0;
+ cgstRate = 0;
+ sgstRate = 0;
+ utgstRate = 0;
+ } else if (field === 'gstRate') {
+ const totalRate = parseFloat(value) || 0;
+ if (taxConfig.isIGST) {
+ igstRate = totalRate;
+ cgstRate = 0;
+ sgstRate = 0;
+ utgstRate = 0;
+ } else {
+ cgstRate = totalRate / 2;
+ if (taxConfig.isUTGST) {
+ utgstRate = totalRate / 2;
+ sgstRate = 0;
+ } else {
+ sgstRate = totalRate / 2;
+ utgstRate = 0;
+ }
+ igstRate = 0;
+ }
+ }
+
+ const calculation = calculateGST(amount, { cgstRate, sgstRate, igstRate, utgstRate }, quantity);
return {
...updatedItem,
amount,
- gstRate: rate,
quantity,
- ...gst
+ ...calculation
};
}
@@ -662,448 +718,325 @@ export function DealerProposalSubmissionModal({
)}
-
- {/* Left Column - Documents */}
-
- {/* Proposal Document Section */}
-
-
- Proposal Document
- Required
-
-
-
-
- Detailed proposal with activity details and requested information
-
-
- documentPolicy.allowedFileTypes.includes(ext.replace('.', ''))).join(',')}
- className="hidden"
- id="proposalDoc"
- onChange={handleProposalDocChange}
- />
-
-
-
+
+ {/* 1. Proposal Document - Column 1 */}
+
+
+ Proposal Document
+ Required
-
- {/* Other Supporting Documents Section */}
-
-
- Other Supporting Documents
- Optional
-
-
-
-
- Any other supporting documents (invoices, receipts, photos, etc.)
-
- 0
- ? 'border-blue-500 bg-blue-50 hover:border-blue-600'
- : 'border-gray-300 hover:border-blue-500 bg-white'
- }`}
- >
- `.${ext}`).join(',')}
- className="hidden"
- id="otherDocs"
- onChange={handleOtherDocsChange}
- />
-
-
- {otherDocuments.length > 0 && (
-
-
- Selected Documents ({otherDocuments.length}):
-
- {otherDocuments.map((file, index) => (
-
-
-
-
- {file.name}
-
-
-
- {canPreviewFile(file) && (
-
- )}
-
-
-
-
- ))}
+
+ documentPolicy.allowedFileTypes.includes(ext.replace('.', ''))).join(',')}
+ className="hidden"
+ id="proposalDoc"
+ onChange={handleProposalDocChange}
+ />
+
+
- {/* Right Column - Planning & Budget */}
-
- {/* Cost Breakup Section */}
-
-
-
- Cost Breakup
- Required
-
+ {/* 2. Timeline for Closure - Column 2 */}
+
+
+ Timeline for Closure
+ Required
+
+
+
+
-
- {costItems.map((item) => (
-
-
- {/* Row 1: Description and Close Button */}
-
-
-
-
- handleCostItemChange(item.id, 'description', e.target.value)
- }
- className="w-full bg-white"
- />
-
-
-
+ {timelineMode === 'date' ? (
+ setExpectedCompletionDate(date || '')}
+ minDate={minDate}
+ placeholderText="dd/mm/yyyy"
+ className="w-full"
+ />
+ ) : (
+ setNumberOfDays(e.target.value)}
+ className="h-10 w-full"
+ />
+ )}
+
+
- {/* Row 2: Numeric Fields */}
-
-
-
+ {/* 3. Cost Breakup Section - Full Width (Row 2) */}
+
+
+
+ Cost Breakup
+ Required
+
+
+ Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state).
+
+
+
+
+
+ {costItems.map((item) => (
+
+
+ {/* Row 1: Description and Close */}
+
+
+
+ handleCostItemChange(item.id, 'description', e.target.value)}
+ className="w-full bg-white shadow-sm"
+ />
+
+
+
+
+ {/* Row 2: Numeric Calculations - Optimization: Narrower widths for GST to save space */}
+
+
+
+
+
- handleCostItemChange(item.id, 'amount', e.target.value)
- }
- className="w-full bg-white"
- />
-
-
-
-
- handleCostItemChange(item.id, 'hsnCode', e.target.value)
- }
- 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 && (
-
- {validateHSNSAC(item.hsnCode, item.isService).message}
-
- )}
-
-
-
-
-
-
-
-
- handleCostItemChange(item.id, 'gstRate', e.target.value)
- }
- className="w-full bg-white"
+ onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)}
+ className="pl-8 bg-white shadow-sm"
/>
+
+
+ handleCostItemChange(item.id, 'hsnCode', e.target.value)}
+ className={`bg-white shadow-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500' : ''}`}
+ />
+
+
+
+
+
+
+
+ handleCostItemChange(item.id, 'cgstRate', e.target.value)}
+ disabled={!taxConfig.isCGST}
+ className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400"
+ />
+
+
+
+ handleCostItemChange(item.id, 'sgstRate', e.target.value)}
+ disabled={!taxConfig.isSGST}
+ className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400"
+ />
+
+
+
+ handleCostItemChange(item.id, 'utgstRate', e.target.value)}
+ disabled={!taxConfig.isUTGST}
+ className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400"
+ />
+
+
+
+ handleCostItemChange(item.id, 'igstRate', e.target.value)}
+ disabled={!taxConfig.isIGST}
+ className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400"
+ />
+
- {item.gstAmt > 0 && (
-
-
- CGST ({item.cgstRate}%):
- ₹{item.cgstAmt.toLocaleString('en-IN')}
-
-
- SGST ({item.sgstRate}%):
- ₹{item.sgstAmt.toLocaleString('en-IN')}
-
-
- GST Total:
- ₹{item.gstAmt.toLocaleString('en-IN')}
-
-
- Item Total:
- ₹{item.totalAmt.toLocaleString('en-IN')}
-
+ {/* Item Summary Row */}
+
+
+ CGST: ₹{item.cgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}
+ {item.sgstAmt > 0 && SGST: ₹{item.sgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}}
+ {item.utgstAmt > 0 && UTGST: ₹{item.utgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}}
+ IGST: ₹{item.igstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}
- )}
-
- ))}
-
-
-
-
-
- Estimated Budget
-
-
- ₹{totalBudget.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
+
+ GST Total: ₹{item.gstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}
+ Item Total: ₹{item.totalAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}
+
+
-
+ ))}
- {/* Timeline for Closure Section */}
-
-
- Timeline for Closure
- Required
-
-
-
-
-
+ {/* Total Budget Card */}
+
+
+
+
+
+
+ Estimated Total Budget
+ Inclusive of all applicable taxes
- {timelineMode === 'date' ? (
-
-
- setExpectedCompletionDate(date || '')}
- minDate={minDate}
- placeholderText="dd/mm/yyyy"
- className="w-full"
- />
-
- ) : (
-
-
- setNumberOfDays(e.target.value)}
- className="h-9 lg:h-10 w-full"
- />
-
- )}
-
-
- {/* Dealer Comments Section */}
-
-
-
- {/* Full Width Sections */}
-
- {/* Warning Message */}
- {!isFormValid && (
-
-
-
- Missing Required Information
-
- Please ensure proposal document, cost breakup, timeline, and dealer comments are provided before submitting.
-
-
+ {/* 4. Other Supporting Docs - Column 1 */}
+
+
+ Other Documents
+ Optional
- )}
+ 0 ? 'border-blue-500 bg-blue-50' : 'border-gray-300 bg-white'}`}
+ >
+ `.${ext}`).join(',')}
+ className="hidden"
+ id="otherDocs"
+ onChange={handleOtherDocsChange}
+ />
+
+
+
+ {otherDocuments.length > 0 && (
+
+ {otherDocuments.map((file, index) => (
+
+
+
+ {file.name}
+
+
+ {canPreviewFile(file) && (
+
+ )}
+
+
+
+
+ ))}
+
+ )}
+
+
+ {/* 5. Dealer Comments - Column 2 */}
+
+
+
+
+ {/* Warning Message */}
+ {!isFormValid && (
+
+
+
+ Missing Required Information
+
+ Please ensure proposal document, cost breakup, timeline, and dealer comments are provided before submitting.
+
+
+
+ )}
@@ -1128,11 +1061,11 @@ export function DealerProposalSubmissionModal({
{/* Standardized File Preview */}
{previewDoc && (
setPreviewDoc(null)}
diff --git a/src/dealer-claim/components/request-detail/modals/InitiatorProposalApprovalModal.tsx b/src/dealer-claim/components/request-detail/modals/InitiatorProposalApprovalModal.tsx
index 3e7d688..97e09d9 100644
--- a/src/dealer-claim/components/request-detail/modals/InitiatorProposalApprovalModal.tsx
+++ b/src/dealer-claim/components/request-detail/modals/InitiatorProposalApprovalModal.tsx
@@ -608,9 +608,8 @@ export function InitiatorProposalApprovalModal({
<>
-
+
Item Description
- Qty
Base
GST
Total
@@ -618,14 +617,11 @@ export function InitiatorProposalApprovalModal({
{costBreakup.map((item: any, index: number) => (
-
+
{item?.description || 'N/A'}
{item?.gstRate ? {item.gstRate}% GST : null}
-
- {item?.quantity || 1}
-
₹{(Number(item?.amount) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
diff --git a/src/utils/gstUtils.ts b/src/utils/gstUtils.ts
new file mode 100644
index 0000000..e4cdb23
--- /dev/null
+++ b/src/utils/gstUtils.ts
@@ -0,0 +1,122 @@
+/**
+ * GST Utility for state validation and tax calculations
+ * Contains state codes and helper functions for determining GST components
+ */
+
+export const STATE_CODES: Record = {
+ '01': 'Jammu and Kashmir',
+ '02': 'Himachal Pradesh',
+ '03': 'Punjab',
+ '04': 'Chandigarh',
+ '05': 'Uttarakhand',
+ '06': 'Haryana',
+ '07': 'Delhi',
+ '08': 'Rajasthan',
+ '09': 'Uttar Pradesh',
+ '10': 'Bihar',
+ '11': 'Sikkim',
+ '12': 'Arunachal Pradesh',
+ '13': 'Nagaland',
+ '14': 'Manipur',
+ '15': 'Mizoram',
+ '16': 'Tripura',
+ '17': 'Meghalaya',
+ '18': 'Assam',
+ '19': 'West Bengal',
+ '20': 'Jharkhand',
+ '21': 'Odisha',
+ '22': 'Chhattisgarh',
+ '23': 'Madhya Pradesh',
+ '24': 'Gujarat',
+ '25': 'Daman and Diu',
+ '26': 'Dadra and Nagar Haveli',
+ '27': 'Maharashtra',
+ '29': 'Karnataka',
+ '30': 'Goa',
+ '31': 'Lakshadweep Islands',
+ '32': 'Kerala',
+ '33': 'Tamil Nadu',
+ '34': 'Pondicherry',
+ '35': 'Andaman and Nicobar',
+ '36': 'Telangana',
+ '37': 'Andhra Pradesh',
+ '38': 'Ladakh',
+ '97': 'Others',
+};
+
+/**
+ * Royal Enfield State Code (Tamil Nadu)
+ */
+export const COMPANY_STATE_CODE = '33';
+
+/**
+ * State codes that use UTGST instead of SGST
+ * Andaman and Nicobar Islands, Chandigarh, Dadra and Nagar Haveli and Daman and Diu, Ladakh, Lakshadweep
+ */
+export const UT_STATE_CODES = new Set(['04', '25', '26', '31', '35', '38']);
+
+/**
+ * Extracts state code from GSTIN
+ * @param gstin The 15-digit GSTIN string
+ * @returns 2-digit state code or null
+ */
+export const getStateCodeFromGSTIN = (gstin: string | undefined | null): string | null => {
+ if (!gstin || gstin.length < 2) return null;
+ const stateCode = gstin.substring(0, 2);
+ return STATE_CODES[stateCode] ? stateCode : null;
+};
+
+/**
+ * Checks if a state code corresponds to a Union Territory (requiring UTGST)
+ * @param stateCode 2-digit state code
+ * @returns boolean
+ */
+export const isUnionTerritory = (stateCode: string | undefined | null): boolean => {
+ if (!stateCode) return false;
+ return UT_STATE_CODES.has(stateCode);
+};
+
+/**
+ * Determines if a transaction is Inter-state (IGST) or Intra-state (CGST+SGST/UTGST)
+ * @param dealerStateCode 2-digit state code of the dealer
+ * @returns true if IGST, false if CGST+SGST/UTGST
+ */
+export const isInterState = (dealerStateCode: string | undefined | null): boolean => {
+ if (!dealerStateCode) return false; // Default to intra-state if unknown
+ return dealerStateCode !== COMPANY_STATE_CODE;
+};
+
+/**
+ * Gets the tax components for a given dealer state
+ * @param dealerStateCode 2-digit state code of the dealer
+ * @returns Object indicating which tax components are active
+ */
+export const getActiveTaxComponents = (dealerStateCode: string | undefined | null) => {
+ if (!dealerStateCode) {
+ return {
+ isIGST: false,
+ isCGST: true,
+ isSGST: true,
+ isUTGST: false,
+ };
+ }
+
+ const isInter = isInterState(dealerStateCode);
+
+ if (isInter) {
+ return {
+ isIGST: true,
+ isCGST: false,
+ isSGST: false,
+ isUTGST: false,
+ };
+ }
+
+ const isUT = isUnionTerritory(dealerStateCode);
+ return {
+ isIGST: false,
+ isCGST: true,
+ isSGST: !isUT,
+ isUTGST: isUT,
+ };
+};
|