Revert "add uploaded Form16 PDF APIs and bulk download support"

This reverts commit d25ffbaf7b.
This commit is contained in:
Aaditya Jaiswal 2026-04-28 19:13:47 +05:30
parent d25ffbaf7b
commit d01e248a35
6 changed files with 807 additions and 1033 deletions

978
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -39,7 +39,6 @@
"@google/generative-ai": "^0.24.1",
"@types/nodemailer": "^7.0.4",
"@types/uuid": "^8.3.4",
"archiver": "^7.0.1",
"axios": "^1.7.9",
"bcryptjs": "^2.4.3",
"bullmq": "^5.63.0",
@ -79,7 +78,6 @@
"zod": "^3.24.1"
},
"devDependencies": {
"@types/archiver": "^7.0.0",
"@types/bcryptjs": "^2.4.6",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.17",

View File

@ -199,58 +199,6 @@ export class Form16Controller {
}
}
/**
* GET /api/v1/form16/uploaded-form16-pdfs
* RE only. List uploaded Form16A PDFs that are successfully submitted and have credit notes.
*/
async listUploadedForm16Pdfs(req: Request, res: Response): Promise<void> {
try {
const result = await form16Service.listUploadedForm16Pdfs({
search: req.query.search as string | undefined,
dealerCode: req.query.dealerCode as string | undefined,
dealerName: req.query.dealerName as string | undefined,
financialYear: req.query.financialYear as string | undefined,
assessmentYear: req.query.assessmentYear as string | undefined,
quarter: req.query.quarter as string | undefined,
uploadedFrom: req.query.uploadedFrom as string | undefined,
uploadedTo: req.query.uploadedTo as string | undefined,
zone: req.query.zone as string | undefined,
region: req.query.region as string | undefined,
limit: req.query.limit != null ? parseInt(String(req.query.limit), 10) : undefined,
offset: req.query.offset != null ? parseInt(String(req.query.offset), 10) : undefined,
});
return ResponseHandler.success(res, result, 'Uploaded Form 16 PDFs fetched');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error('[Form16Controller] listUploadedForm16Pdfs error:', error);
return ResponseHandler.error(res, 'Failed to fetch uploaded Form 16 PDFs', 500, errorMessage);
}
}
/**
* POST /api/v1/form16/uploaded-form16-pdfs/bulk-download-links
* RE only. Return document links for selected submission ids (used by FE bulk download action).
*/
async getUploadedForm16PdfBulkDownloadLinks(req: Request, res: Response): Promise<void> {
try {
const idsRaw = Array.isArray((req.body as any)?.submissionIds) ? (req.body as any).submissionIds : [];
const requested = idsRaw
.map((v: unknown) => Number(v))
.filter((n: number) => Number.isInteger(n) && n > 0);
if (!requested.length) {
return ResponseHandler.error(res, 'submissionIds is required', 400);
}
const files = await form16Service.getUploadedForm16PdfLinksBySubmissionIds(requested);
return ResponseHandler.success(res, { files }, 'Bulk download links generated');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error('[Form16Controller] getUploadedForm16PdfBulkDownloadLinks error:', error);
return ResponseHandler.error(res, 'Failed to prepare bulk download links', 500, errorMessage);
}
}
/**
* GET /api/v1/form16/dealer/submissions
* Dealer only. List Form 16 submissions for the authenticated dealer (pending/failed for Pending Submissions page).
@ -400,11 +348,6 @@ export class Form16Controller {
async get26asDashboard(req: Request, res: Response): Promise<void> {
try {
const dashboard = await form16Service.getForm16DashboardData();
logger.info('[Form16Controller] 26AS dashboard served', {
yearRows: dashboard.yearWise?.length || 0,
zoneRows: dashboard.zoneWise?.length || 0,
zoneHierarchy: dashboard.zoneHierarchy?.length || 0,
});
return ResponseHandler.success(res, dashboard, 'Form16A dashboard fetched');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';

View File

@ -1,50 +0,0 @@
import { QueryInterface } from 'sequelize';
module.exports = {
up: async (queryInterface: QueryInterface) => {
// Speeds latest-submission window ordering per dealer+FY+quarter.
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS idx_form16a_submissions_dashboard_latest
ON form16a_submissions (
dealer_code,
financial_year,
quarter,
version DESC,
submitted_date DESC,
created_at DESC,
id DESC
);
`);
// Speeds join between latest submissions and 26AS quarter snapshots.
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS idx_form16a_submissions_tan_fy_quarter
ON form16a_submissions (tan_number, financial_year, quarter);
`);
// Speeds normalized active dealer lookup used by dashboard aggregation.
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS idx_dealers_active_normalized_code_region
ON dealers (
(TRIM(COALESCE(NULLIF(sales_code, ''), NULLIF(dlrcode, '')))),
region
)
WHERE is_active = true;
`);
},
down: async (queryInterface: QueryInterface) => {
await queryInterface.sequelize.query(`
DROP INDEX IF EXISTS idx_dealers_active_normalized_code_region;
`);
await queryInterface.sequelize.query(`
DROP INDEX IF EXISTS idx_form16a_submissions_tan_fy_quarter;
`);
await queryInterface.sequelize.query(`
DROP INDEX IF EXISTS idx_form16a_submissions_dashboard_latest;
`);
},
};

View File

@ -108,18 +108,6 @@ router.get(
requireForm16SubmissionAccess,
asyncHandler(form16Controller.listDebitNotes.bind(form16Controller))
);
router.get(
'/uploaded-form16-pdfs',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.listUploadedForm16Pdfs.bind(form16Controller))
);
router.post(
'/uploaded-form16-pdfs/bulk-download-links',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.getUploadedForm16PdfBulkDownloadLinks.bind(form16Controller))
);
router.get(
'/debit-notes/:id/sap-response',
requireForm16ReOnly,

View File

@ -1836,21 +1836,11 @@ export async function listDealerPendingQuarters(userId: string) {
`SELECT financial_year, quarter, MIN(created_at) AS min_created FROM tds_26as_entries GROUP BY financial_year, quarter`,
{ type: QueryTypes.SELECT }
));
const twentySixAsAmounts = (await sequelize.query<{ financial_year: string; quarter: string; total_amount: number | string }>(
`SELECT financial_year, quarter, COALESCE(SUM(tax_deducted), 0) AS total_amount FROM tds_26as_entries GROUP BY financial_year, quarter`,
{ type: QueryTypes.SELECT }
));
const minDateByKey = new Map<string, Date>();
for (const row of twentySixAsMinDates) {
const key = `${row.financial_year}|${row.quarter}`;
if (row.min_created) minDateByKey.set(key, new Date(row.min_created));
}
const amountByKey = new Map<string, number>();
for (const row of twentySixAsAmounts) {
const key = `${row.financial_year}|${row.quarter}`;
const amount = Number(row.total_amount ?? 0);
amountByKey.set(key, Number.isFinite(amount) ? amount : 0);
}
const dealerSubmissions = await Form16aSubmission.findAll({
where: { dealerCode },
@ -1887,7 +1877,6 @@ export async function listDealerPendingQuarters(userId: string) {
days_remaining: number | null;
days_overdue: number | null;
days_since_26as_uploaded: number | null;
twenty_six_as_amount: number;
}> = [];
const now = new Date();
const oneDayMs = 24 * 60 * 60 * 1000;
@ -1912,7 +1901,6 @@ export async function listDealerPendingQuarters(userId: string) {
const daysSince26AsUploaded = twentySixAsMin
? Math.floor((now.getTime() - twentySixAsMin.getTime()) / oneDayMs)
: null;
const twentySixAsAmount = amountByKey.get(key) ?? 0;
result.push({
financial_year: financialYear,
quarter,
@ -1925,7 +1913,6 @@ export async function listDealerPendingQuarters(userId: string) {
days_remaining: daysRemaining,
days_overdue: daysOverdue,
days_since_26as_uploaded: daysSince26AsUploaded,
twenty_six_as_amount: twentySixAsAmount,
});
}
result.sort((a, b) => {
@ -1938,282 +1925,6 @@ export async function listDealerPendingQuarters(userId: string) {
return result;
}
export interface Form16UploadedPdfFilters {
search?: string;
dealerCode?: string;
dealerName?: string;
financialYear?: string;
assessmentYear?: string;
quarter?: string;
uploadedFrom?: string;
uploadedTo?: string;
zone?: string;
region?: string;
limit?: number;
offset?: number;
}
export interface Form16UploadedPdfRow {
submissionId: number;
requestId: string;
creditNoteNumber: string;
dealerName: string;
dealerCode: string;
region: string;
zone: string;
financialYear: string;
assessmentYear: string;
quarter: string;
amount: number;
uploadedDate: string | null;
documentUrl: string;
}
type Form16UploadedPdfSqlRow = {
submission_id: number;
request_id: string;
credit_note_number: string;
dealer_name: string | null;
dealer_code: string;
region: string | null;
zone: string | null;
financial_year: string;
assessment_year: string | null;
quarter: string;
amount: string | number;
uploaded_date: Date | string | null;
document_url: string;
};
/**
* RE only. List uploaded Form16A PDFs that were successfully processed and have credit notes.
* Includes dealer + geography columns and supports filter/search for management views.
*/
export async function listUploadedForm16Pdfs(filters?: Form16UploadedPdfFilters): Promise<{
rows: Form16UploadedPdfRow[];
total: number;
}> {
const replacements: Record<string, unknown> = {
limit: Math.min(Math.max(Number(filters?.limit || 50), 1), 500),
offset: Math.max(Number(filters?.offset || 0), 0),
};
const where: string[] = [
`s.validation_status = 'success'`,
`COALESCE(TRIM(s.document_url), '') <> ''`,
];
const search = String(filters?.search || '').trim().toLowerCase();
if (search) {
replacements.search = `%${search}%`;
where.push(
`(
LOWER(COALESCE(d.dealership, d.dealer_principal_name, s.dealer_code)) LIKE :search
OR LOWER(COALESCE(s.dealer_code, '')) LIKE :search
OR LOWER(COALESCE(s.financial_year, '')) LIKE :search
OR LOWER(COALESCE(s.quarter, '')) LIKE :search
OR LOWER(COALESCE(c.credit_note_number, '')) LIKE :search
)`
);
}
const dealerCode = String(filters?.dealerCode || '').trim();
if (dealerCode) {
replacements.dealerCode = `%${dealerCode.toLowerCase()}%`;
where.push(`LOWER(COALESCE(s.dealer_code, '')) LIKE :dealerCode`);
}
const dealerName = String(filters?.dealerName || '').trim();
if (dealerName) {
replacements.dealerName = `%${dealerName.toLowerCase()}%`;
where.push(`LOWER(COALESCE(d.dealership, d.dealer_principal_name, s.dealer_code)) LIKE :dealerName`);
}
const financialYear = normalizeFinancialYear(filters?.financialYear || '') || String(filters?.financialYear || '').trim();
if (financialYear) {
replacements.financialYear = financialYear;
where.push(`s.financial_year = :financialYear`);
}
const quarter = normalizeQuarter(filters?.quarter || '') || String(filters?.quarter || '').trim().toUpperCase();
if (quarter) {
replacements.quarter = quarter;
where.push(`s.quarter = :quarter`);
}
const region = String(filters?.region || '').trim();
if (region) {
replacements.region = `%${region.toLowerCase()}%`;
where.push(`LOWER(COALESCE(d.region, 'UNKNOWN')) LIKE :region`);
}
const zone = String(filters?.zone || '').trim();
if (zone) {
replacements.zone = zone.toUpperCase();
where.push(
`(CASE
WHEN UPPER(LEFT(COALESCE(d.region, ''), 1)) IN ('N','S','E','W','C')
THEN UPPER(LEFT(d.region, 1))
ELSE 'UNKNOWN'
END) = :zone`
);
}
const uploadedFrom = String(filters?.uploadedFrom || '').trim();
if (uploadedFrom) {
replacements.uploadedFrom = uploadedFrom;
where.push(`DATE(COALESCE(s.submitted_date, s.created_at)) >= :uploadedFrom`);
}
const uploadedTo = String(filters?.uploadedTo || '').trim();
if (uploadedTo) {
replacements.uploadedTo = uploadedTo;
where.push(`DATE(COALESCE(s.submitted_date, s.created_at)) <= :uploadedTo`);
}
const assessmentYear = String(filters?.assessmentYear || '').trim();
if (assessmentYear) {
replacements.assessmentYear = assessmentYear.toLowerCase();
where.push(
`LOWER(
CASE
WHEN s.financial_year ~ '^[0-9]{4}-[0-9]{2}$'
THEN ((SUBSTRING(s.financial_year, 1, 4)::int + 1)::text || '-' || LPAD((((SUBSTRING(s.financial_year, 6, 2)::int + 1) % 100)::text), 2, '0'))
ELSE s.financial_year
END
) LIKE '%' || :assessmentYear || '%'`
);
}
const whereSql = where.length ? `WHERE ${where.join('\n AND ')}` : '';
const baseFromSql = `
FROM form16a_submissions s
INNER JOIN form_16_credit_notes c ON c.submission_id = s.id
LEFT JOIN LATERAL (
SELECT
d.dealership,
d.dealer_principal_name,
d.region
FROM dealers d
WHERE d.is_active = true
AND (
COALESCE(d.sales_code, '') = COALESCE(s.dealer_code, '')
OR COALESCE(d.dlrcode, '') = COALESCE(s.dealer_code, '')
)
ORDER BY CASE WHEN COALESCE(d.sales_code, '') = COALESCE(s.dealer_code, '') THEN 0 ELSE 1 END
LIMIT 1
) d ON true
`;
const rows = await sequelize.query<Form16UploadedPdfSqlRow>(
`
SELECT
s.id AS submission_id,
s.request_id,
c.credit_note_number,
COALESCE(d.dealership, d.dealer_principal_name, s.dealer_code) AS dealer_name,
s.dealer_code,
COALESCE(d.region, 'UNKNOWN') AS region,
CASE
WHEN UPPER(LEFT(COALESCE(d.region, ''), 1)) = 'N' THEN 'North'
WHEN UPPER(LEFT(COALESCE(d.region, ''), 1)) = 'S' THEN 'South'
WHEN UPPER(LEFT(COALESCE(d.region, ''), 1)) = 'E' THEN 'East'
WHEN UPPER(LEFT(COALESCE(d.region, ''), 1)) = 'W' THEN 'West'
WHEN UPPER(LEFT(COALESCE(d.region, ''), 1)) = 'C' THEN 'Central'
ELSE 'Unknown'
END AS zone,
s.financial_year,
CASE
WHEN s.financial_year ~ '^[0-9]{4}-[0-9]{2}$'
THEN ((SUBSTRING(s.financial_year, 1, 4)::int + 1)::text || '-' || LPAD((((SUBSTRING(s.financial_year, 6, 2)::int + 1) % 100)::text), 2, '0'))
ELSE s.financial_year
END AS assessment_year,
s.quarter,
s.total_amount AS amount,
COALESCE(s.submitted_date, s.created_at) AS uploaded_date,
s.document_url
${baseFromSql}
${whereSql}
ORDER BY COALESCE(s.submitted_date, s.created_at) DESC, s.id DESC
LIMIT :limit OFFSET :offset
`,
{ replacements, type: QueryTypes.SELECT }
);
const countRows = await sequelize.query<{ total: string }>(
`
SELECT COUNT(1)::text AS total
${baseFromSql}
${whereSql}
`,
{ replacements, type: QueryTypes.SELECT }
);
const total = Number(countRows?.[0]?.total || 0);
return {
rows: rows.map((r) => ({
submissionId: Number(r.submission_id),
requestId: r.request_id,
creditNoteNumber: r.credit_note_number,
dealerName: String(r.dealer_name || r.dealer_code || '—'),
dealerCode: String(r.dealer_code || '—'),
region: String(r.region || 'UNKNOWN'),
zone: String(r.zone || 'Unknown'),
financialYear: String(r.financial_year || ''),
assessmentYear: String(r.assessment_year || r.financial_year || ''),
quarter: String(r.quarter || ''),
amount: Number(r.amount || 0),
uploadedDate: r.uploaded_date ? new Date(r.uploaded_date).toISOString() : null,
documentUrl: String(r.document_url || ''),
})),
total,
};
}
export async function getUploadedForm16PdfLinksBySubmissionIds(submissionIds: number[]): Promise<Array<{
submissionId: number;
creditNoteNumber: string;
dealerCode: string;
quarter: string;
financialYear: string;
documentUrl: string;
}>> {
const ids = (submissionIds || []).map((v) => Number(v)).filter((n) => Number.isInteger(n) && n > 0);
if (!ids.length) return [];
const rows = await sequelize.query<{
submission_id: number;
credit_note_number: string;
dealer_code: string;
quarter: string;
financial_year: string;
document_url: string;
}>(
`
SELECT
s.id AS submission_id,
c.credit_note_number,
s.dealer_code,
s.quarter,
s.financial_year,
s.document_url
FROM form16a_submissions s
INNER JOIN form_16_credit_notes c ON c.submission_id = s.id
WHERE s.id IN (:ids)
AND s.validation_status = 'success'
AND COALESCE(TRIM(s.document_url), '') <> ''
ORDER BY s.id DESC
`,
{ replacements: { ids }, type: QueryTypes.SELECT }
);
return rows.map((r) => ({
submissionId: Number(r.submission_id),
creditNoteNumber: r.credit_note_number,
dealerCode: r.dealer_code,
quarter: r.quarter,
financialYear: r.financial_year,
documentUrl: r.document_url,
}));
}
/**
* RE only. Cancel a Form 16 submission: set submission status to cancelled and workflow to REJECTED.
*/
@ -3407,31 +3118,11 @@ export interface Form16DashboardBreakdownRow {
pendingDealerCount: number;
}
export interface Form16DashboardDealerRow {
dealerCode: string;
dealerName: string;
regionId: string;
totalAmount: number;
submittedAmount: number;
pendingAmount: number;
}
export interface Form16DashboardZoneRegionNode extends Form16DashboardBreakdownRow {
regionId: string;
dealers: Form16DashboardDealerRow[];
}
export interface Form16DashboardZoneNode extends Form16DashboardBreakdownRow {
zoneCode: string;
regions: Form16DashboardZoneRegionNode[];
}
export interface Form16DashboardData {
kpi: Form16DashboardKpi;
overall: Form16DashboardOverall;
yearWise: Form16DashboardBreakdownRow[];
zoneWise: Form16DashboardBreakdownRow[];
zoneHierarchy: Form16DashboardZoneNode[];
}
/**
@ -3448,172 +3139,64 @@ export async function getForm16DashboardData(): Promise<Form16DashboardData> {
return Number.isFinite(n) ? n : 0;
};
const zoneSortRank = (zoneCode: string): number => {
const z = String(zoneCode || '').toUpperCase();
if (z === 'N') return 1;
if (z === 'C') return 2;
if (z === 'W') return 3;
if (z === 'E') return 4;
if (z === 'S') return 5;
return 99;
};
const zoneName = (zoneCode: string): string => {
const z = String(zoneCode || '').toUpperCase();
if (z === 'N') return 'North';
if (z === 'C') return 'Central';
if (z === 'W') return 'West';
if (z === 'E') return 'East';
if (z === 'S') return 'South';
return z || 'Unknown';
};
const regionSortValue = (regionId: string): number => {
const r = String(regionId || '').toUpperCase();
const m = /^([A-Z]+)(\d+)$/.exec(r);
if (!m) return Number.MAX_SAFE_INTEGER;
return parseInt(m[2], 10);
};
// Dealer master is the base universe for dashboard (include all active dealers).
const dealerUniverseRaw = await sequelize.query<{
dealer_code: string;
dealer_name: string | null;
region_id: string | null;
}>(
`
SELECT DISTINCT
TRIM(COALESCE(d.dlrcode, '')) AS dealer_code,
COALESCE(NULLIF(TRIM(d.dealership), ''), TRIM(COALESCE(d.dlrcode, ''))) AS dealer_name,
UPPER(TRIM(COALESCE(d.region, ''))) AS region_id
FROM dealers d
WHERE d.is_active = true
AND TRIM(COALESCE(d.dlrcode, '')) <> ''
`,
{ type: QueryTypes.SELECT }
);
const dealerUniverse = (dealerUniverseRaw || []).map((r) => ({
dealerCode: String(r.dealer_code || '').trim(),
dealerName: String(r.dealer_name || r.dealer_code || '').trim() || 'Unknown Dealer',
regionId: String(r.region_id || '').trim().toUpperCase() || 'UNKNOWN',
}));
const detailRowsRaw = await sequelize.query<{
dealer_code: string;
dealer_name: string | null;
region_id: string | null;
financial_year: string;
quarter: string;
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 dealer_meta AS (
WITH active_dealers AS (
SELECT DISTINCT
TRIM(COALESCE(d.dlrcode, '')) AS dealer_code,
COALESCE(NULLIF(TRIM(d.dealership), ''), TRIM(COALESCE(d.dlrcode, ''))) AS dealer_name,
UPPER(TRIM(COALESCE(d.region, ''))) AS region_id
TRIM(COALESCE(NULLIF(d.sales_code, ''), NULLIF(d.dlrcode, ''))) AS dealer_code
FROM dealers d
WHERE d.is_active = true
AND TRIM(COALESCE(d.dlrcode, '')) <> ''
AND TRIM(COALESCE(NULLIF(d.sales_code, ''), NULLIF(d.dlrcode, ''))) <> ''
),
latest_submissions AS (
SELECT
s.id,
TRIM(COALESCE(s.dealer_code, '')) AS dealer_code,
s.dealer_code,
s.financial_year,
s.quarter,
s.tan_number,
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
WHERE TRIM(COALESCE(s.dealer_code, '')) <> ''
INNER JOIN active_dealers ad ON ad.dealer_code = s.dealer_code
),
latest_base AS (
SELECT id, dealer_code, financial_year, quarter, tan_number, total_amount
SELECT id, dealer_code, financial_year, quarter, total_amount
FROM latest_submissions
WHERE rn = 1
),
latest_snapshots AS (
SELECT tan_number, financial_year, quarter, aggregated_amount
FROM (
SELECT
ss.tan_number,
ss.financial_year,
ss.quarter,
COALESCE(ss.aggregated_amount, 0)::numeric AS aggregated_amount,
ROW_NUMBER() OVER (
PARTITION BY ss.tan_number, ss.financial_year, ss.quarter
ORDER BY ss.id DESC, ss.created_at DESC
) AS rn
FROM form_16_26as_quarter_snapshots ss
) t
WHERE t.rn = 1
),
credit_by_submission AS (
SELECT cn.submission_id, SUM(COALESCE(cn.amount, 0))::numeric AS submitted_amount
FROM form_16_credit_notes cn
GROUP BY cn.submission_id
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
lb.dealer_code,
COALESCE(dm.dealer_name, lb.dealer_code) AS dealer_name,
COALESCE(dm.region_id, 'UNKNOWN') AS region_id,
lb.financial_year,
lb.quarter,
COALESCE(ls.aggregated_amount, lb.total_amount, 0)::numeric AS total_amount,
COALESCE(cbs.submitted_amount, 0)::numeric AS submitted_amount
FROM latest_base lb
LEFT JOIN dealer_meta dm ON dm.dealer_code = lb.dealer_code
LEFT JOIN latest_snapshots ls
ON ls.tan_number = lb.tan_number
AND ls.financial_year = lb.financial_year
AND ls.quarter = lb.quarter
LEFT JOIN credit_by_submission cbs ON cbs.submission_id = lb.id
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 detailRows = (detailRowsRaw || []).map((r) => ({
dealerCode: String(r.dealer_code || '').trim(),
dealerName: String(r.dealer_name || r.dealer_code || '').trim() || 'Unknown Dealer',
regionId: String(r.region_id || '').trim().toUpperCase() || 'UNKNOWN',
financialYear: String(r.financial_year || '').trim(),
quarter: String(r.quarter || '').trim(),
totalAmount: Math.max(0, toNum(r.total_amount)),
submittedAmount: Math.max(0, toNum(r.submitted_amount)),
}));
const dealerAgg = new Map<string, { dealerName: string; regionId: string; totalAmount: number; submittedAmount: number }>();
for (const d of dealerUniverse) {
dealerAgg.set(d.dealerCode, {
dealerName: d.dealerName,
regionId: d.regionId,
totalAmount: 0,
submittedAmount: 0,
});
}
for (const r of detailRows) {
const key = r.dealerCode;
const prev = dealerAgg.get(key) || {
dealerName: r.dealerName || key,
regionId: r.regionId || 'UNKNOWN',
totalAmount: 0,
submittedAmount: 0,
};
prev.totalAmount += r.totalAmount;
prev.submittedAmount += r.submittedAmount;
dealerAgg.set(key, prev);
}
const totalAmount = Array.from(dealerAgg.values()).reduce((sum, r) => sum + r.totalAmount, 0);
const submittedAmount = Array.from(dealerAgg.values()).reduce((sum, r) => sum + r.submittedAmount, 0);
const totalDealers = dealerAgg.size;
const submittedDealerCount = Array.from(dealerAgg.values()).filter((r) => r.submittedAmount > 0).length;
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);
@ -3622,136 +3205,153 @@ export async function getForm16DashboardData(): Promise<Form16DashboardData> {
return Math.max(0, Math.min(100, Math.round((part / whole) * 100)));
};
const yearMap = new Map<string, { totalAmount: number; submittedAmount: number; dealers: Set<string>; submittedDealers: Set<string> }>();
for (const r of detailRows) {
const y = r.financialYear || 'Unknown';
const ag = yearMap.get(y) || { totalAmount: 0, submittedAmount: 0, dealers: new Set<string>(), submittedDealers: new Set<string>() };
ag.totalAmount += r.totalAmount;
ag.submittedAmount += r.submittedAmount;
ag.dealers.add(r.dealerCode);
if (r.submittedAmount > 0) ag.submittedDealers.add(r.dealerCode);
yearMap.set(y, ag);
}
const yearWise = Array.from(yearMap.entries())
.sort((a, b) => b[0].localeCompare(a[0]))
.map(([label, ag]) => {
const dealerCount = ag.dealers.size;
const submittedDealerCount = ag.submittedDealers.size;
return {
label,
totalAmount: ag.totalAmount,
dealerCount,
submittedAmount: ag.submittedAmount,
submittedDealerCount,
pendingAmount: Math.max(0, ag.totalAmount - ag.submittedAmount),
pendingDealerCount: Math.max(0, dealerCount - submittedDealerCount),
};
});
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 dealerRegionMap = new Map<string, { dealerName: string; regionId: string; totalAmount: number; submittedAmount: number }>();
for (const [dealerCode, v] of dealerAgg.entries()) {
dealerRegionMap.set(dealerCode, {
dealerName: v.dealerName,
regionId: v.regionId,
totalAmount: v.totalAmount,
submittedAmount: v.submittedAmount,
});
}
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 zoneMap = new Map<string, { totalAmount: number; submittedAmount: number; dealers: Set<string>; submittedDealers: Set<string>; regions: Map<string, Form16DashboardZoneRegionNode> }>();
for (const [dealerCode, d] of dealerRegionMap.entries()) {
const regionId = String(d.regionId || '').toUpperCase();
const zoneCode = (/^[A-Z]+/.exec(regionId)?.[0] || 'UNKNOWN').toUpperCase();
const zone = zoneMap.get(zoneCode) || {
totalAmount: 0,
submittedAmount: 0,
dealers: new Set<string>(),
submittedDealers: new Set<string>(),
regions: new Map<string, Form16DashboardZoneRegionNode>(),
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),
};
zone.totalAmount += d.totalAmount;
zone.submittedAmount += d.submittedAmount;
zone.dealers.add(dealerCode);
if (d.submittedAmount > 0) zone.submittedDealers.add(dealerCode);
});
const regionNode = zone.regions.get(regionId) || {
regionId,
label: regionId,
totalAmount: 0,
dealerCount: 0,
submittedAmount: 0,
submittedDealerCount: 0,
pendingAmount: 0,
pendingDealerCount: 0,
dealers: [],
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),
};
regionNode.totalAmount += d.totalAmount;
regionNode.submittedAmount += d.submittedAmount;
regionNode.dealers.push({
dealerCode,
dealerName: d.dealerName || dealerCode,
regionId,
totalAmount: d.totalAmount,
submittedAmount: d.submittedAmount,
pendingAmount: Math.max(0, d.totalAmount - d.submittedAmount),
});
zone.regions.set(regionId, regionNode);
zoneMap.set(zoneCode, zone);
}
const zoneHierarchy: Form16DashboardZoneNode[] = Array.from(zoneMap.entries())
.map(([zoneCode, z]) => {
const regions = Array.from(z.regions.values())
.map((r) => {
const submittedDealerCount = r.dealers.filter((d) => d.submittedAmount > 0).length;
const dealerCount = r.dealers.length;
const dealers = [...r.dealers].sort((a, b) => a.dealerCode.localeCompare(b.dealerCode));
return {
...r,
dealers,
dealerCount,
submittedDealerCount,
pendingAmount: Math.max(0, r.totalAmount - r.submittedAmount),
pendingDealerCount: Math.max(0, dealerCount - submittedDealerCount),
};
})
.sort((a, b) => {
const da = regionSortValue(a.regionId);
const db = regionSortValue(b.regionId);
if (da !== db) return da - db;
return a.regionId.localeCompare(b.regionId);
});
const dealerCount = z.dealers.size;
const submittedDealerCount = z.submittedDealers.size;
return {
zoneCode,
label: zoneName(zoneCode),
totalAmount: z.totalAmount,
dealerCount,
submittedAmount: z.submittedAmount,
submittedDealerCount,
pendingAmount: Math.max(0, z.totalAmount - z.submittedAmount),
pendingDealerCount: Math.max(0, dealerCount - submittedDealerCount),
regions,
};
})
.sort((a, b) => {
const ra = zoneSortRank(a.zoneCode);
const rb = zoneSortRank(b.zoneCode);
if (ra !== rb) return ra - rb;
return a.label.localeCompare(b.label);
});
const zoneWise = zoneHierarchy.map((z) => ({
label: z.label,
totalAmount: z.totalAmount,
dealerCount: z.dealerCount,
submittedAmount: z.submittedAmount,
submittedDealerCount: z.submittedDealerCount,
pendingAmount: z.pendingAmount,
pendingDealerCount: z.pendingDealerCount,
}));
});
return {
kpi: {
@ -3770,6 +3370,5 @@ export async function getForm16DashboardData(): Promise<Form16DashboardData> {
},
yearWise,
zoneWise,
zoneHierarchy,
};
}