196 lines
4.6 KiB
TypeScript
196 lines
4.6 KiB
TypeScript
import { Router } from 'express';
|
|
import { body, param } from 'express-validator';
|
|
import multer from 'multer';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import crypto from 'crypto';
|
|
import { tendersController } from './tenders.controller';
|
|
import { authenticate, authorize } from '../../shared/middleware/auth';
|
|
import { validate } from '../../shared/middleware/validation';
|
|
import { config } from '../../config';
|
|
|
|
const router = Router();
|
|
|
|
const uploadDir = path.join(config.upload.path, 'tenders');
|
|
if (!fs.existsSync(uploadDir)) {
|
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
}
|
|
|
|
const storage = multer.diskStorage({
|
|
destination: (_req, _file, cb) => cb(null, uploadDir),
|
|
filename: (_req, file, cb) => {
|
|
const safeName = (file.originalname || 'file').replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
cb(null, `${crypto.randomUUID()}-${safeName}`);
|
|
},
|
|
});
|
|
const upload = multer({
|
|
storage,
|
|
limits: { fileSize: config.upload.maxFileSize },
|
|
});
|
|
|
|
|
|
// View attachment
|
|
router.get(
|
|
'/attachments/:attachmentId/view',
|
|
param('attachmentId').isUUID(),
|
|
validate,
|
|
tendersController.viewAttachment
|
|
)
|
|
|
|
router.use(authenticate);
|
|
|
|
// Enum/lookup routes (no resource id) - place before /:id routes
|
|
router.get(
|
|
'/source-values',
|
|
authorize('tenders', 'tenders', 'read'),
|
|
tendersController.getSourceValues
|
|
);
|
|
router.get(
|
|
'/announcement-type-values',
|
|
authorize('tenders', 'tenders', 'read'),
|
|
tendersController.getAnnouncementTypeValues
|
|
);
|
|
router.get(
|
|
'/directive-type-values',
|
|
authorize('tenders', 'tenders', 'read'),
|
|
tendersController.getDirectiveTypeValues
|
|
);
|
|
|
|
router.post(
|
|
'/check-duplicates',
|
|
authorize('tenders', 'tenders', 'create'),
|
|
[
|
|
body('issuingBodyName').optional().trim(),
|
|
body('title').optional().trim(),
|
|
body('tenderNumber').optional().trim(),
|
|
body('termsValue').optional().isNumeric(),
|
|
body('bondValue').optional().isNumeric(),
|
|
body('announcementDate').optional().isISO8601(),
|
|
body('closingDate').optional().isISO8601(),
|
|
],
|
|
validate,
|
|
tendersController.checkDuplicates
|
|
);
|
|
|
|
// List & create tenders
|
|
router.get(
|
|
'/',
|
|
authorize('tenders', 'tenders', 'read'),
|
|
tendersController.findAll
|
|
);
|
|
router.post(
|
|
'/',
|
|
authorize('tenders', 'tenders', 'create'),
|
|
[
|
|
body('tenderNumber').notEmpty().trim(),
|
|
body('issuingBodyName').notEmpty().trim(),
|
|
body('title').notEmpty().trim(),
|
|
body('termsValue').isNumeric(),
|
|
body('bondValue').isNumeric(),
|
|
body('announcementDate').isISO8601(),
|
|
body('closingDate').isISO8601(),
|
|
body('source').notEmpty(),
|
|
body('announcementType').notEmpty(),
|
|
],
|
|
validate,
|
|
tendersController.create
|
|
);
|
|
|
|
// Tender by id
|
|
router.get(
|
|
'/:id',
|
|
authorize('tenders', 'tenders', 'read'),
|
|
param('id').isUUID(),
|
|
validate,
|
|
tendersController.findById
|
|
);
|
|
router.put(
|
|
'/:id',
|
|
authorize('tenders', 'tenders', 'update'),
|
|
param('id').isUUID(),
|
|
validate,
|
|
tendersController.update
|
|
);
|
|
|
|
// Tender history
|
|
router.get(
|
|
'/:id/history',
|
|
authorize('tenders', 'tenders', 'read'),
|
|
param('id').isUUID(),
|
|
validate,
|
|
tendersController.getHistory
|
|
);
|
|
|
|
// Convert to deal
|
|
router.post(
|
|
'/:id/convert-to-deal',
|
|
authorize('tenders', 'tenders', 'update'),
|
|
[
|
|
param('id').isUUID(),
|
|
body('contactId').isUUID(),
|
|
body('pipelineId').isUUID(),
|
|
body('ownerId').optional().isUUID(),
|
|
],
|
|
validate,
|
|
tendersController.convertToDeal
|
|
);
|
|
|
|
// Directives
|
|
router.post(
|
|
'/:id/directives',
|
|
authorize('tenders', 'directives', 'create'),
|
|
[
|
|
param('id').isUUID(),
|
|
body('type').isIn(['BUY_TERMS', 'VISIT_CLIENT', 'MEET_COMMITTEE', 'PREPARE_TO_BID']),
|
|
body('assignedToEmployeeId').isUUID(),
|
|
body('notes').optional().trim(),
|
|
],
|
|
validate,
|
|
tendersController.createDirective
|
|
);
|
|
|
|
// Update directive (e.g. complete task) - route with directiveId
|
|
router.put(
|
|
'/directives/:directiveId',
|
|
authorize('tenders', 'directives', 'update'),
|
|
[
|
|
param('directiveId').isUUID(),
|
|
body('status').optional().isIn(['PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED']),
|
|
body('completionNotes').optional().trim(),
|
|
],
|
|
validate,
|
|
tendersController.updateDirective
|
|
);
|
|
|
|
// File uploads
|
|
router.post(
|
|
'/:id/attachments',
|
|
authorize('tenders', 'tenders', 'update'),
|
|
param('id').isUUID(),
|
|
validate,
|
|
upload.single('file'),
|
|
tendersController.uploadTenderAttachment
|
|
);
|
|
|
|
router.post(
|
|
'/directives/:directiveId/attachments',
|
|
authorize('tenders', 'directives', 'update'),
|
|
param('directiveId').isUUID(),
|
|
validate,
|
|
upload.single('file'),
|
|
tendersController.uploadDirectiveAttachment
|
|
);
|
|
|
|
export default router;
|
|
|
|
|
|
|
|
// Delete attachment
|
|
router.delete(
|
|
'/attachments/:attachmentId',
|
|
authorize('tenders', 'tenders', 'update'),
|
|
param('attachmentId').isUUID(),
|
|
validate,
|
|
tendersController.deleteAttachment
|
|
)
|