Re_Backend/src/routes/form16.routes.ts
2026-03-18 12:59:20 +05:30

230 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Router } from 'express';
import multer from 'multer';
import path from 'path';
import fs from 'fs';
import { authenticateToken } from '../middlewares/auth.middleware';
import { requireForm16SubmissionAccess, requireForm1626AsAccess, requireForm16ReOnly } from '../middlewares/form16Permission.middleware';
import { form16Controller } from '../controllers/form16.controller';
import { form16SapController } from '../controllers/form16Sap.controller';
import { asyncHandler } from '../middlewares/errorHandler.middleware';
import { UPLOAD_DIR } from '../config/storage';
const router = Router();
// REform16 pattern: disk storage to uploads dir (path.join(__dirname, '../../uploads') → we use UPLOAD_DIR/form16-extract)
const form16ExtractDir = path.join(UPLOAD_DIR, 'form16-extract');
if (!fs.existsSync(form16ExtractDir)) {
fs.mkdirSync(form16ExtractDir, { recursive: true });
}
const storage = multer.diskStorage({
destination: (_req, _file, cb) => cb(null, form16ExtractDir),
filename: (_req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
cb(null, `form16a-${uniqueSuffix}${path.extname(file.originalname || '.pdf')}`);
},
});
// Same multer config as REform16: disk storage, 10MB, PDF only, field 'document'
const uploadExtract = multer({
storage,
limits: { fileSize: 10 * 1024 * 1024 },
fileFilter: (_req, file, cb) => {
const ext = path.extname(file.originalname || '').toLowerCase();
if (ext === '.pdf') {
cb(null, true);
} else {
cb(new Error('Only PDF files are allowed'));
}
},
});
// Memory storage for submission (needs buffer for GCS upload)
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 15 * 1024 * 1024 },
});
// 26AS upload: .txt only, 5MB, memory storage (parse then bulk insert)
const upload26asTxt = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (_req, file, cb) => {
const ext = path.extname(file.originalname || '').toLowerCase();
const isTxt = ext === '.txt' || (file.mimetype && (file.mimetype === 'text/plain' || file.mimetype === 'application/octet-stream'));
if (isTxt) {
cb(null, true);
} else {
cb(new Error('Only .txt files are allowed for 26AS upload'));
}
},
});
router.use(authenticateToken);
// Permissions (API-driven from admin config; used by frontend to show/hide Form 16 and 26AS)
router.get(
'/permissions',
asyncHandler(form16Controller.getPermissions.bind(form16Controller))
);
// Form 16 submission data (who can see: submissionViewerEmails from admin config; dealers always allowed)
router.get(
'/credit-notes',
requireForm16SubmissionAccess,
asyncHandler(form16Controller.listCreditNotes.bind(form16Controller))
);
// RE only: list debit notes
router.get(
'/debit-notes',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.listDebitNotes.bind(form16Controller))
);
router.get(
'/debit-notes/:id/sap-response',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.viewDebitNoteSapResponse.bind(form16Controller))
);
router.get(
'/credit-notes/:id',
requireForm16SubmissionAccess,
asyncHandler(form16Controller.getCreditNoteById.bind(form16Controller))
);
router.get(
'/credit-notes/:id/download',
requireForm16SubmissionAccess,
asyncHandler(form16Controller.downloadCreditNote.bind(form16Controller))
);
router.get(
'/requests/:requestId/credit-note',
requireForm16SubmissionAccess,
asyncHandler(form16Controller.getCreditNoteByRequest.bind(form16Controller))
);
// RE only: Form 16 request actions (cancel, resubmission needed)
router.post(
'/requests/:requestId/cancel-submission',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.cancelForm16Submission.bind(form16Controller))
);
router.post(
'/requests/:requestId/resubmission-needed',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.setForm16ResubmissionNeeded.bind(form16Controller))
);
// Dealer: contact admin when 26AS missing for quarter
router.post(
'/requests/:requestId/contact-admin',
requireForm16SubmissionAccess,
asyncHandler(form16Controller.contactAdmin.bind(form16Controller))
);
// Pull SAP outgoing responses now (credit/debit). Used by Pull button in UI.
router.post(
'/sap/pull',
requireForm16SubmissionAccess,
asyncHandler(form16SapController.pull.bind(form16SapController))
);
// Form 16 SAP simulation (credit note / debit note). Replace with real SAP when integrating.
router.post(
'/sap-simulate/credit-note',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.sapSimulateCreditNote.bind(form16Controller))
);
router.post(
'/sap-simulate/debit-note',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.sapSimulateDebitNote.bind(form16Controller))
);
// Dealer-only: pending submissions and pending quarters (Form 16 Pending Submissions page)
router.get(
'/dealer/submissions',
requireForm16SubmissionAccess,
asyncHandler(form16Controller.listDealerSubmissions.bind(form16Controller))
);
router.get(
'/dealer/pending-quarters',
requireForm16SubmissionAccess,
asyncHandler(form16Controller.listDealerPendingQuarters.bind(form16Controller))
);
router.get(
'/non-submitted-dealers',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.listNonSubmittedDealers.bind(form16Controller))
);
router.post(
'/non-submitted-dealers/notify',
requireForm16ReOnly,
requireForm16SubmissionAccess,
asyncHandler(form16Controller.notifyNonSubmittedDealer.bind(form16Controller))
);
// 26AS (who can see: twentySixAsViewerEmails from admin config)
router.get(
'/26as',
requireForm1626AsAccess,
asyncHandler(form16Controller.list26as.bind(form16Controller))
);
router.post(
'/26as',
requireForm1626AsAccess,
asyncHandler(form16Controller.create26as.bind(form16Controller))
);
router.put(
'/26as/:id',
requireForm1626AsAccess,
asyncHandler(form16Controller.update26as.bind(form16Controller))
);
router.delete(
'/26as/:id',
requireForm1626AsAccess,
asyncHandler(form16Controller.delete26as.bind(form16Controller))
);
router.get(
'/26as/upload-history',
requireForm1626AsAccess,
asyncHandler(form16Controller.get26asUploadHistory.bind(form16Controller))
);
router.post(
'/26as/upload',
requireForm1626AsAccess,
upload26asTxt.single('file'),
asyncHandler(form16Controller.upload26as.bind(form16Controller))
);
// Extract dealers/RE with Form 16 submission access
router.post(
'/extract',
requireForm16SubmissionAccess,
uploadExtract.single('document'),
asyncHandler(form16Controller.extractOcr.bind(form16Controller))
);
// Create Form 16 submission dealers/RE with Form 16 submission access
router.post(
'/submissions',
requireForm16SubmissionAccess,
upload.single('document'),
asyncHandler(form16Controller.createSubmission.bind(form16Controller))
);
// Dev only: send a Form 16 test notification (in-app + email via Ethereal) to current user
if (process.env.NODE_ENV !== 'production') {
router.post(
'/test-notification',
asyncHandler(form16Controller.testNotification.bind(form16Controller))
);
}
export default router;