248 lines
9.5 KiB
TypeScript
248 lines
9.5 KiB
TypeScript
/**
|
||
* Seed demo data: Form 16 submissions and other workflow requests.
|
||
* Data is inserted into database tables only (no hardcoded data in app code).
|
||
*
|
||
* Prerequisites:
|
||
* - Run seed:admin-user (for admin user)
|
||
* - Run seed:dealer-user (for dealer user + dealer record for Form 16)
|
||
*
|
||
* Usage: npm run seed:demo-requests
|
||
*/
|
||
|
||
import { Op } from 'sequelize';
|
||
import { sequelize } from '../config/database';
|
||
import {
|
||
User,
|
||
WorkflowRequest,
|
||
Form16aSubmission,
|
||
Form16CreditNote,
|
||
Activity,
|
||
Document,
|
||
Dealer,
|
||
} from '../models';
|
||
import { Priority, WorkflowStatus } from '../types/common.types';
|
||
import { generateRequestNumber } from '../utils/helpers';
|
||
import logger from '../utils/logger';
|
||
|
||
const DEMO_MARKER_TITLE_PREFIX = 'Form 16A - 2024-25'; // Used to detect existing demo Form 16 data
|
||
const DEMO_CUSTOM_TITLE = '[Demo] Sample workflow request';
|
||
|
||
async function getOrResolveUsers(): Promise<{ adminUser: User | null; dealerUser: User | null; dealerCode: string | null }> {
|
||
const adminUser = await User.findOne({ where: { email: 'admin@example.com' } });
|
||
const dealerUser = await User.findOne({ where: { email: 'testreflow@example.com' } });
|
||
let dealerCode: string | null = null;
|
||
if (dealerUser) {
|
||
const dealer = await Dealer.findOne({
|
||
where: { dealerPrincipalEmailId: 'testreflow@example.com', isActive: true },
|
||
attributes: ['salesCode', 'dlrcode'],
|
||
});
|
||
dealerCode = dealer?.salesCode ?? dealer?.dlrcode ?? null;
|
||
}
|
||
return { adminUser, dealerUser, dealerCode };
|
||
}
|
||
|
||
async function hasExistingDemoForm16(): Promise<boolean> {
|
||
const count = await WorkflowRequest.count({
|
||
where: {
|
||
templateType: 'FORM_16',
|
||
title: { [Op.like]: `${DEMO_MARKER_TITLE_PREFIX}%` },
|
||
},
|
||
});
|
||
return count >= 2;
|
||
}
|
||
|
||
/** Demo dealers used for "non-submitted dealers" list: active dealers with no Form 16 submission for the demo FY/quarter. */
|
||
const DEMO_NON_SUBMITTED_DEALERS = [
|
||
{ dlrcode: 'DEMO-NOSUB-001', dealership: 'Demo Motors Mumbai', dealerPrincipalName: 'Demo Mumbai', dealerPrincipalEmailId: 'demo.nosub.1@example.com', state: 'Maharashtra', city: 'Mumbai' },
|
||
{ dlrcode: 'DEMO-NOSUB-002', dealership: 'Demo Enfield Delhi', dealerPrincipalName: 'Demo Delhi', dealerPrincipalEmailId: 'demo.nosub.2@example.com', state: 'Delhi', city: 'New Delhi' },
|
||
{ dlrcode: 'DEMO-NOSUB-003', dealership: 'Demo Royal Bangalore', dealerPrincipalName: 'Demo Bangalore', dealerPrincipalEmailId: 'demo.nosub.3@example.com', state: 'Karnataka', city: 'Bengaluru' },
|
||
];
|
||
|
||
async function ensureDemoNonSubmittedDealers(): Promise<void> {
|
||
for (const data of DEMO_NON_SUBMITTED_DEALERS) {
|
||
const [dealer] = await Dealer.findOrCreate({
|
||
where: { dlrcode: data.dlrcode },
|
||
defaults: {
|
||
...data,
|
||
salesCode: data.dlrcode,
|
||
isActive: true,
|
||
} as any,
|
||
});
|
||
if (dealer && !dealer.isActive) {
|
||
await dealer.update({ isActive: true });
|
||
}
|
||
}
|
||
logger.info('[Seed Demo] Demo non-submitted dealers ensured (they have no Form 16 submissions for 2024-25).');
|
||
}
|
||
|
||
async function seedDemoRequests(): Promise<void> {
|
||
// Always ensure demo dealers exist first (for both submission and non-submitted lists).
|
||
// These dealers are in the dealers table; only the test dealer gets Form 16 submissions below.
|
||
await ensureDemoNonSubmittedDealers();
|
||
|
||
const { adminUser, dealerUser, dealerCode } = await getOrResolveUsers();
|
||
const initiatorId = adminUser?.userId ?? dealerUser?.userId;
|
||
if (!initiatorId) {
|
||
logger.warn('[Seed Demo] No admin or dealer user found. Run seed:admin-user and seed:dealer-user first.');
|
||
return;
|
||
}
|
||
|
||
if (await hasExistingDemoForm16()) {
|
||
logger.info('[Seed Demo] Demo Form 16 requests already present. Skipping to avoid duplicates.');
|
||
return;
|
||
}
|
||
|
||
const now = new Date();
|
||
|
||
// ---- 1) Create a few CUSTOM (non–Form 16) requests so "other requests" are visible ----
|
||
for (let i = 1; i <= 2; i++) {
|
||
const requestNumber = await generateRequestNumber();
|
||
await WorkflowRequest.create({
|
||
requestNumber,
|
||
initiatorId,
|
||
templateType: 'CUSTOM',
|
||
workflowType: 'NON_TEMPLATIZED',
|
||
title: `${DEMO_CUSTOM_TITLE} ${i}`,
|
||
description: `Demo workflow request for testing. Created by seed script.`,
|
||
priority: Priority.STANDARD,
|
||
status: i === 1 ? WorkflowStatus.PENDING : WorkflowStatus.PENDING,
|
||
currentLevel: 1,
|
||
totalLevels: 1,
|
||
totalTatHours: 48,
|
||
submissionDate: now,
|
||
isDraft: false,
|
||
isDeleted: false,
|
||
isPaused: false,
|
||
});
|
||
}
|
||
|
||
// ---- 2) Create Form 16 requests + submissions (and optional credit notes / activity / docs) ----
|
||
if (!dealerUser || !dealerCode) {
|
||
logger.warn('[Seed Demo] Dealer user or dealer code missing. Form 16 demo requests skipped. Run seed:dealer-user.');
|
||
} else {
|
||
const form16InitiatorId = dealerUser.userId;
|
||
const form16Demos = [
|
||
{ fy: '2024-25', quarter: 'Q1', deductor: 'Royal Enfield Motors Ltd', tds: 15000, total: 150000, cnAmount: 15000, withCreditNote: true, withdrawn: false },
|
||
{ fy: '2024-25', quarter: 'Q2', deductor: 'Royal Enfield Motors Ltd', tds: 18000, total: 180000, cnAmount: 18000, withCreditNote: true, withdrawn: true },
|
||
{ fy: '2024-25', quarter: 'Q3', deductor: 'Royal Enfield Motors Ltd', tds: 12000, total: 120000, cnAmount: null, withCreditNote: false, withdrawn: false },
|
||
];
|
||
|
||
for (const demo of form16Demos) {
|
||
const requestNumber = await generateRequestNumber();
|
||
const title = `Form 16A - ${demo.fy} ${demo.quarter}`;
|
||
const description = `Form 16A TDS certificate submission. Deductor: ${demo.deductor}.`;
|
||
|
||
const workflow = await WorkflowRequest.create({
|
||
requestNumber,
|
||
initiatorId: form16InitiatorId,
|
||
templateType: 'FORM_16',
|
||
workflowType: 'FORM_16',
|
||
title,
|
||
description,
|
||
priority: Priority.STANDARD,
|
||
status: demo.withCreditNote && !demo.withdrawn ? WorkflowStatus.CLOSED : WorkflowStatus.PENDING,
|
||
currentLevel: 1,
|
||
totalLevels: 1,
|
||
totalTatHours: 0,
|
||
submissionDate: now,
|
||
closureDate: demo.withCreditNote && !demo.withdrawn ? now : undefined,
|
||
isDraft: false,
|
||
isDeleted: false,
|
||
isPaused: false,
|
||
});
|
||
|
||
const requestId = (workflow as any).requestId;
|
||
const form16aNumber = `DEMO-F16-${demo.quarter}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
||
|
||
const submission = await Form16aSubmission.create({
|
||
requestId,
|
||
dealerCode,
|
||
form16aNumber,
|
||
financialYear: demo.fy,
|
||
quarter: demo.quarter,
|
||
version: 1,
|
||
tdsAmount: demo.tds,
|
||
totalAmount: demo.total,
|
||
tanNumber: 'BLRE00001E',
|
||
deductorName: demo.deductor,
|
||
documentUrl: 'https://example.com/demo-form16a.pdf',
|
||
status: 'pending',
|
||
submittedDate: now,
|
||
});
|
||
|
||
// Activity: dealer submitted Form 16
|
||
await Activity.create({
|
||
requestId,
|
||
userId: form16InitiatorId,
|
||
userName: (dealerUser as any).displayName || (dealerUser as any).firstName + ' ' + (dealerUser as any).lastName || 'Dealer',
|
||
activityType: 'FORM_16_SUBMITTED',
|
||
activityDescription: `Dealer submitted Form 16A for ${demo.fy} ${demo.quarter}. Certificate: ${form16aNumber}.`,
|
||
activityCategory: 'submission',
|
||
isSystemEvent: false,
|
||
});
|
||
|
||
// Document: attached Form 16 (placeholder so Docs tab shows an entry)
|
||
await Document.create({
|
||
requestId,
|
||
uploadedBy: form16InitiatorId,
|
||
fileName: `form16a-${demo.quarter}.pdf`,
|
||
originalFileName: `Form16A_${demo.fy}_${demo.quarter}.pdf`,
|
||
fileType: 'application/pdf',
|
||
fileExtension: 'pdf',
|
||
fileSize: 1024,
|
||
filePath: `form16-demo/${requestId}/form16a.pdf`,
|
||
mimeType: 'application/pdf',
|
||
checksum: 'demo-checksum-' + requestId.slice(0, 8),
|
||
isGoogleDoc: false,
|
||
category: 'SUPPORTING',
|
||
version: 1,
|
||
isDeleted: false,
|
||
downloadCount: 0,
|
||
uploadedAt: now,
|
||
});
|
||
|
||
if (demo.withCreditNote && demo.cnAmount != null) {
|
||
const creditNote = await Form16CreditNote.create({
|
||
submissionId: submission.id,
|
||
creditNoteNumber: `DEMO-CN-${demo.quarter}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
||
sapDocumentNumber: `SAP-${demo.quarter}-DEMO`,
|
||
amount: demo.cnAmount,
|
||
issueDate: now.toISOString().slice(0, 10) as unknown as Date,
|
||
financialYear: demo.fy,
|
||
quarter: demo.quarter,
|
||
status: demo.withdrawn ? 'withdrawn' : 'issued',
|
||
issuedBy: adminUser?.userId,
|
||
createdAt: now,
|
||
updatedAt: now,
|
||
});
|
||
await Activity.create({
|
||
requestId,
|
||
userId: adminUser?.userId ?? undefined,
|
||
userName: 'RE Admin',
|
||
activityType: demo.withdrawn ? 'CREDIT_NOTE_WITHDRAWN' : 'CREDIT_NOTE_ISSUED',
|
||
activityDescription: demo.withdrawn
|
||
? `Credit note ${creditNote.creditNoteNumber} was withdrawn by RE user.`
|
||
: `Credit note ${creditNote.creditNoteNumber} generated for ${demo.fy} ${demo.quarter}.`,
|
||
activityCategory: 'workflow',
|
||
isSystemEvent: false,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
logger.info('[Seed Demo] Demo requests, Form 16 submissions, and non-submitted dealers demo data created successfully.');
|
||
}
|
||
|
||
if (require.main === module) {
|
||
sequelize
|
||
.authenticate()
|
||
.then(() => seedDemoRequests())
|
||
.then(() => process.exit(0))
|
||
.catch((err) => {
|
||
logger.error('[Seed Demo] Failed:', err);
|
||
process.exit(1);
|
||
});
|
||
}
|
||
|
||
export { seedDemoRequests, ensureDemoNonSubmittedDealers };
|