dashboard added
This commit is contained in:
parent
34c488ae16
commit
876ec26e97
@ -1 +1 @@
|
|||||||
import{a as s}from"./index-B_32yGxr.js";import"./radix-vendor-CLtqm-Ae.js";import"./charts-vendor-CmYZJIYl.js";import"./utils-vendor-BTBPSQfW.js";import"./ui-vendor-DgwXkk2Y.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-HW_ujxKo.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
import{a as s}from"./index-BVr6jLdd.js";import"./radix-vendor-CLtqm-Ae.js";import"./charts-vendor-CmYZJIYl.js";import"./utils-vendor-BTBPSQfW.js";import"./ui-vendor-DgwXkk2Y.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-HW_ujxKo.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
build/assets/index-f-qcsO2P.css
Normal file
1
build/assets/index-f-qcsO2P.css
Normal file
File diff suppressed because one or more lines are too long
@ -13,7 +13,7 @@
|
|||||||
<!-- Preload essential fonts and icons -->
|
<!-- Preload essential fonts and icons -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<script type="module" crossorigin src="/assets/index-B_32yGxr.js"></script>
|
<script type="module" crossorigin src="/assets/index-BVr6jLdd.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-CmYZJIYl.js">
|
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-CmYZJIYl.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CLtqm-Ae.js">
|
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CLtqm-Ae.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-BTBPSQfW.js">
|
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-BTBPSQfW.js">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
|
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/router-vendor-HW_ujxKo.js">
|
<link rel="modulepreload" crossorigin href="/assets/router-vendor-HW_ujxKo.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Dr_mmbQV.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-f-qcsO2P.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -341,6 +341,21 @@ export class Form16Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/v1/form16/26as/dashboard
|
||||||
|
* RE only. Aggregated Form16A dashboard (collection/submission status + year/zone breakdown).
|
||||||
|
*/
|
||||||
|
async get26asDashboard(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const dashboard = await form16Service.getForm16DashboardData();
|
||||||
|
return ResponseHandler.success(res, dashboard, 'Form16A dashboard fetched');
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
logger.error('[Form16Controller] get26asDashboard error:', error);
|
||||||
|
return ResponseHandler.error(res, 'Failed to fetch Form16A dashboard', 500, errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/v1/form16/26as
|
* POST /api/v1/form16/26as
|
||||||
* RE only. Create a 26AS TDS entry.
|
* RE only. Create a 26AS TDS entry.
|
||||||
|
|||||||
@ -213,6 +213,12 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 26AS (who can see: twentySixAsViewerEmails from admin config)
|
// 26AS (who can see: twentySixAsViewerEmails from admin config)
|
||||||
|
router.get(
|
||||||
|
'/26as/dashboard',
|
||||||
|
requireForm16ReOnly,
|
||||||
|
requireForm1626AsAccess,
|
||||||
|
asyncHandler(form16Controller.get26asDashboard.bind(form16Controller))
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/26as',
|
'/26as',
|
||||||
requireForm16ReOnly,
|
requireForm16ReOnly,
|
||||||
|
|||||||
@ -874,8 +874,17 @@ export class AuthService {
|
|||||||
throw new Error('Redirect URI is required');
|
throw new Error('Redirect URI is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalize = (s: string) => s.trim().replace(/\/+$/, '');
|
||||||
|
const providedRedirectUri = normalize(redirectUri);
|
||||||
|
const frontendBase = process.env.FRONTEND_URL ? normalize(process.env.FRONTEND_URL) : '';
|
||||||
|
const canonicalRedirectUri = frontendBase ? `${frontendBase}/login/callback` : providedRedirectUri;
|
||||||
|
const isSecureEnv = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'uat';
|
||||||
|
const effectiveRedirectUri = isSecureEnv ? canonicalRedirectUri : providedRedirectUri;
|
||||||
|
|
||||||
logger.info('Exchanging code with Okta', {
|
logger.info('Exchanging code with Okta', {
|
||||||
redirectUri,
|
redirectUri: effectiveRedirectUri,
|
||||||
|
providedRedirectUri,
|
||||||
|
canonicalRedirectUri,
|
||||||
codePrefix: code.substring(0, 10) + '...',
|
codePrefix: code.substring(0, 10) + '...',
|
||||||
oktaDomain: ssoConfig.oktaDomain,
|
oktaDomain: ssoConfig.oktaDomain,
|
||||||
clientId: ssoConfig.oktaClientId,
|
clientId: ssoConfig.oktaClientId,
|
||||||
@ -891,7 +900,7 @@ export class AuthService {
|
|||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: 'authorization_code',
|
grant_type: 'authorization_code',
|
||||||
code,
|
code,
|
||||||
redirect_uri: redirectUri, // Frontend URL (e.g., http://localhost:3000/login/callback)
|
redirect_uri: effectiveRedirectUri, // Must match authorize request redirect_uri exactly
|
||||||
client_id: ssoConfig.oktaClientId,
|
client_id: ssoConfig.oktaClientId,
|
||||||
client_secret: ssoConfig.oktaClientSecret,
|
client_secret: ssoConfig.oktaClientSecret,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -3039,3 +3039,284 @@ export async function list26asUploadHistory(
|
|||||||
});
|
});
|
||||||
return { rows: mapped, total: count };
|
return { rows: mapped, total: count };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Form16DashboardKpi {
|
||||||
|
collectionPct: number;
|
||||||
|
pendingPct: number;
|
||||||
|
submittedPct: number;
|
||||||
|
submissionPendingPct: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Form16DashboardOverall {
|
||||||
|
totalAmount: number;
|
||||||
|
submittedAmount: number;
|
||||||
|
pendingAmount: number;
|
||||||
|
totalDealers: number;
|
||||||
|
submittedDealerCount: number;
|
||||||
|
pendingDealerCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Form16DashboardBreakdownRow {
|
||||||
|
label: string;
|
||||||
|
totalAmount: number;
|
||||||
|
dealerCount: number;
|
||||||
|
submittedAmount: number;
|
||||||
|
submittedDealerCount: number;
|
||||||
|
pendingAmount: number;
|
||||||
|
pendingDealerCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Form16DashboardData {
|
||||||
|
kpi: Form16DashboardKpi;
|
||||||
|
overall: Form16DashboardOverall;
|
||||||
|
yearWise: Form16DashboardBreakdownRow[];
|
||||||
|
zoneWise: Form16DashboardBreakdownRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form16A dashboard for RE users.
|
||||||
|
* Uses real DB data:
|
||||||
|
* - dealer universe from active dealers
|
||||||
|
* - latest submission per dealer+FY+quarter
|
||||||
|
* - submitted/credited via form_16_credit_notes
|
||||||
|
* Zone mapping follows dealer region code prefix: N* -> North, S* -> South, E* -> East, W* -> West, C* -> Central.
|
||||||
|
*/
|
||||||
|
export async function getForm16DashboardData(): Promise<Form16DashboardData> {
|
||||||
|
const toNum = (v: unknown): number => {
|
||||||
|
const n = Number(v ?? 0);
|
||||||
|
return Number.isFinite(n) ? n : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [overallRow] = await sequelize.query<{
|
||||||
|
total_amount: number | string | null;
|
||||||
|
submitted_amount: number | string | null;
|
||||||
|
total_dealers: number | string | null;
|
||||||
|
submitted_dealer_count: number | string | null;
|
||||||
|
}>(
|
||||||
|
`
|
||||||
|
WITH active_dealers AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
TRIM(COALESCE(NULLIF(d.sales_code, ''), NULLIF(d.dlrcode, ''))) AS dealer_code
|
||||||
|
FROM dealers d
|
||||||
|
WHERE d.is_active = true
|
||||||
|
AND TRIM(COALESCE(NULLIF(d.sales_code, ''), NULLIF(d.dlrcode, ''))) <> ''
|
||||||
|
),
|
||||||
|
latest_submissions AS (
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
s.dealer_code,
|
||||||
|
s.financial_year,
|
||||||
|
s.quarter,
|
||||||
|
COALESCE(s.total_amount, 0)::numeric AS total_amount,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY s.dealer_code, s.financial_year, s.quarter
|
||||||
|
ORDER BY COALESCE(s.version, 1) DESC, COALESCE(s.submitted_date, s.created_at) DESC, s.id DESC
|
||||||
|
) AS rn
|
||||||
|
FROM form16a_submissions s
|
||||||
|
INNER JOIN active_dealers ad ON ad.dealer_code = s.dealer_code
|
||||||
|
),
|
||||||
|
latest_base AS (
|
||||||
|
SELECT id, dealer_code, financial_year, quarter, total_amount
|
||||||
|
FROM latest_submissions
|
||||||
|
WHERE rn = 1
|
||||||
|
),
|
||||||
|
submitted_by_dealer AS (
|
||||||
|
SELECT
|
||||||
|
lb.dealer_code,
|
||||||
|
SUM(COALESCE(cn.amount, 0))::numeric AS submitted_amount
|
||||||
|
FROM latest_base lb
|
||||||
|
LEFT JOIN form_16_credit_notes cn ON cn.submission_id = lb.id
|
||||||
|
GROUP BY lb.dealer_code
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
COALESCE((SELECT SUM(lb.total_amount) FROM latest_base lb), 0) AS total_amount,
|
||||||
|
COALESCE((SELECT SUM(sbd.submitted_amount) FROM submitted_by_dealer sbd), 0) AS submitted_amount,
|
||||||
|
COALESCE((SELECT COUNT(*) FROM active_dealers), 0) AS total_dealers,
|
||||||
|
COALESCE((
|
||||||
|
SELECT COUNT(DISTINCT sbd.dealer_code)
|
||||||
|
FROM submitted_by_dealer sbd
|
||||||
|
WHERE sbd.submitted_amount > 0
|
||||||
|
), 0) AS submitted_dealer_count
|
||||||
|
`,
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalAmount = toNum(overallRow?.total_amount);
|
||||||
|
const submittedAmount = toNum(overallRow?.submitted_amount);
|
||||||
|
const totalDealers = Math.max(0, Math.trunc(toNum(overallRow?.total_dealers)));
|
||||||
|
const submittedDealerCount = Math.max(0, Math.trunc(toNum(overallRow?.submitted_dealer_count)));
|
||||||
|
const pendingDealerCount = Math.max(0, totalDealers - submittedDealerCount);
|
||||||
|
const pendingAmount = Math.max(0, totalAmount - submittedAmount);
|
||||||
|
|
||||||
|
const toPct = (part: number, whole: number): number => {
|
||||||
|
if (!whole || whole <= 0) return 0;
|
||||||
|
return Math.max(0, Math.min(100, Math.round((part / whole) * 100)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const yearRowsRaw = await sequelize.query<{
|
||||||
|
label: string;
|
||||||
|
total_amount: number | string | null;
|
||||||
|
dealer_count: number | string | null;
|
||||||
|
submitted_amount: number | string | null;
|
||||||
|
submitted_dealer_count: number | string | null;
|
||||||
|
}>(
|
||||||
|
`
|
||||||
|
WITH active_dealers AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
TRIM(COALESCE(NULLIF(d.sales_code, ''), NULLIF(d.dlrcode, ''))) AS dealer_code
|
||||||
|
FROM dealers d
|
||||||
|
WHERE d.is_active = true
|
||||||
|
AND TRIM(COALESCE(NULLIF(d.sales_code, ''), NULLIF(d.dlrcode, ''))) <> ''
|
||||||
|
),
|
||||||
|
latest_submissions AS (
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
s.dealer_code,
|
||||||
|
s.financial_year,
|
||||||
|
s.quarter,
|
||||||
|
COALESCE(s.total_amount, 0)::numeric AS total_amount,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY s.dealer_code, s.financial_year, s.quarter
|
||||||
|
ORDER BY COALESCE(s.version, 1) DESC, COALESCE(s.submitted_date, s.created_at) DESC, s.id DESC
|
||||||
|
) AS rn
|
||||||
|
FROM form16a_submissions s
|
||||||
|
INNER JOIN active_dealers ad ON ad.dealer_code = s.dealer_code
|
||||||
|
),
|
||||||
|
latest_base AS (
|
||||||
|
SELECT id, dealer_code, financial_year, quarter, total_amount
|
||||||
|
FROM latest_submissions
|
||||||
|
WHERE rn = 1
|
||||||
|
),
|
||||||
|
by_year AS (
|
||||||
|
SELECT
|
||||||
|
lb.financial_year AS label,
|
||||||
|
SUM(lb.total_amount)::numeric AS total_amount,
|
||||||
|
COUNT(DISTINCT lb.dealer_code) AS dealer_count,
|
||||||
|
SUM(COALESCE(cn.amount, 0))::numeric AS submitted_amount,
|
||||||
|
COUNT(DISTINCT CASE WHEN COALESCE(cn.amount, 0) > 0 THEN lb.dealer_code END) AS submitted_dealer_count
|
||||||
|
FROM latest_base lb
|
||||||
|
LEFT JOIN form_16_credit_notes cn ON cn.submission_id = lb.id
|
||||||
|
GROUP BY lb.financial_year
|
||||||
|
)
|
||||||
|
SELECT * FROM by_year
|
||||||
|
ORDER BY label DESC
|
||||||
|
`,
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
|
||||||
|
const zoneRowsRaw = await sequelize.query<{
|
||||||
|
label: string;
|
||||||
|
total_amount: number | string | null;
|
||||||
|
dealer_count: number | string | null;
|
||||||
|
submitted_amount: number | string | null;
|
||||||
|
submitted_dealer_count: number | string | null;
|
||||||
|
}>(
|
||||||
|
`
|
||||||
|
WITH active_dealers AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
TRIM(COALESCE(NULLIF(d.sales_code, ''), NULLIF(d.dlrcode, ''))) AS dealer_code,
|
||||||
|
CASE
|
||||||
|
WHEN UPPER(COALESCE(d.region, '')) LIKE 'N%' THEN 'North'
|
||||||
|
WHEN UPPER(COALESCE(d.region, '')) LIKE 'S%' THEN 'South'
|
||||||
|
WHEN UPPER(COALESCE(d.region, '')) LIKE 'E%' THEN 'East'
|
||||||
|
WHEN UPPER(COALESCE(d.region, '')) LIKE 'W%' THEN 'West'
|
||||||
|
WHEN UPPER(COALESCE(d.region, '')) LIKE 'C%' THEN 'Central'
|
||||||
|
ELSE 'Unknown'
|
||||||
|
END AS zone
|
||||||
|
FROM dealers d
|
||||||
|
WHERE d.is_active = true
|
||||||
|
AND TRIM(COALESCE(NULLIF(d.sales_code, ''), NULLIF(d.dlrcode, ''))) <> ''
|
||||||
|
),
|
||||||
|
latest_submissions AS (
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
s.dealer_code,
|
||||||
|
COALESCE(s.total_amount, 0)::numeric AS total_amount,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY s.dealer_code, s.financial_year, s.quarter
|
||||||
|
ORDER BY COALESCE(s.version, 1) DESC, COALESCE(s.submitted_date, s.created_at) DESC, s.id DESC
|
||||||
|
) AS rn
|
||||||
|
FROM form16a_submissions s
|
||||||
|
INNER JOIN active_dealers ad ON ad.dealer_code = s.dealer_code
|
||||||
|
),
|
||||||
|
latest_base AS (
|
||||||
|
SELECT id, dealer_code, total_amount
|
||||||
|
FROM latest_submissions
|
||||||
|
WHERE rn = 1
|
||||||
|
),
|
||||||
|
by_zone AS (
|
||||||
|
SELECT
|
||||||
|
ad.zone AS label,
|
||||||
|
SUM(COALESCE(lb.total_amount, 0))::numeric AS total_amount,
|
||||||
|
COUNT(DISTINCT ad.dealer_code) AS dealer_count,
|
||||||
|
SUM(COALESCE(cn.amount, 0))::numeric AS submitted_amount,
|
||||||
|
COUNT(DISTINCT CASE WHEN COALESCE(cn.amount, 0) > 0 THEN ad.dealer_code END) AS submitted_dealer_count
|
||||||
|
FROM active_dealers ad
|
||||||
|
LEFT JOIN latest_base lb ON lb.dealer_code = ad.dealer_code
|
||||||
|
LEFT JOIN form_16_credit_notes cn ON cn.submission_id = lb.id
|
||||||
|
GROUP BY ad.zone
|
||||||
|
)
|
||||||
|
SELECT * FROM by_zone
|
||||||
|
ORDER BY CASE label
|
||||||
|
WHEN 'North' THEN 1
|
||||||
|
WHEN 'Central' THEN 2
|
||||||
|
WHEN 'West' THEN 3
|
||||||
|
WHEN 'East' THEN 4
|
||||||
|
WHEN 'South' THEN 5
|
||||||
|
ELSE 99
|
||||||
|
END, label
|
||||||
|
`,
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
|
||||||
|
const yearWise = (yearRowsRaw || []).map((r) => {
|
||||||
|
const totalAmountRow = toNum(r.total_amount);
|
||||||
|
const submittedAmountRow = toNum(r.submitted_amount);
|
||||||
|
const dealerCountRow = Math.max(0, Math.trunc(toNum(r.dealer_count)));
|
||||||
|
const submittedDealerCountRow = Math.max(0, Math.trunc(toNum(r.submitted_dealer_count)));
|
||||||
|
return {
|
||||||
|
label: r.label,
|
||||||
|
totalAmount: totalAmountRow,
|
||||||
|
dealerCount: dealerCountRow,
|
||||||
|
submittedAmount: submittedAmountRow,
|
||||||
|
submittedDealerCount: submittedDealerCountRow,
|
||||||
|
pendingAmount: Math.max(0, totalAmountRow - submittedAmountRow),
|
||||||
|
pendingDealerCount: Math.max(0, dealerCountRow - submittedDealerCountRow),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const zoneWise = (zoneRowsRaw || []).map((r) => {
|
||||||
|
const totalAmountRow = toNum(r.total_amount);
|
||||||
|
const submittedAmountRow = toNum(r.submitted_amount);
|
||||||
|
const dealerCountRow = Math.max(0, Math.trunc(toNum(r.dealer_count)));
|
||||||
|
const submittedDealerCountRow = Math.max(0, Math.trunc(toNum(r.submitted_dealer_count)));
|
||||||
|
return {
|
||||||
|
label: r.label,
|
||||||
|
totalAmount: totalAmountRow,
|
||||||
|
dealerCount: dealerCountRow,
|
||||||
|
submittedAmount: submittedAmountRow,
|
||||||
|
submittedDealerCount: submittedDealerCountRow,
|
||||||
|
pendingAmount: Math.max(0, totalAmountRow - submittedAmountRow),
|
||||||
|
pendingDealerCount: Math.max(0, dealerCountRow - submittedDealerCountRow),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
kpi: {
|
||||||
|
collectionPct: toPct(submittedDealerCount, totalDealers),
|
||||||
|
pendingPct: toPct(pendingDealerCount, totalDealers),
|
||||||
|
submittedPct: toPct(submittedAmount, totalAmount),
|
||||||
|
submissionPendingPct: toPct(pendingAmount, totalAmount),
|
||||||
|
},
|
||||||
|
overall: {
|
||||||
|
totalAmount,
|
||||||
|
submittedAmount,
|
||||||
|
pendingAmount,
|
||||||
|
totalDealers,
|
||||||
|
submittedDealerCount,
|
||||||
|
pendingDealerCount,
|
||||||
|
},
|
||||||
|
yearWise,
|
||||||
|
zoneWise,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user