feat(tenders): add Tender Management module (SRS, backend, frontend)
- SRS document: docs/SRS_TENDER_MANAGEMENT.md - Prisma: Tender, TenderDirective models; Deal.sourceTenderId; Attachment.tenderId/tenderDirectiveId - Backend: tenders module (CRUD, duplicate check, directives, notifications, file upload, convert-to-deal) - Frontend: tenders list, detail, create/edit forms, directives, convert to deal, i18n (en/ar), dashboard card - Seed: tenders permissions for admin and sales positions - Auth: admin.service findFirst for email check (Prisma compatibility) Made-with: Cursor
This commit is contained in:
232
backend/src/modules/tenders/tenders.controller.ts
Normal file
232
backend/src/modules/tenders/tenders.controller.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { Response, NextFunction } from 'express';
|
||||
import { AuthRequest } from '../../shared/middleware/auth';
|
||||
import { tendersService } from './tenders.service';
|
||||
import { ResponseFormatter } from '../../shared/utils/responseFormatter';
|
||||
|
||||
export class TendersController {
|
||||
async checkDuplicates(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const duplicates = await tendersService.findPossibleDuplicates(req.body);
|
||||
res.json(ResponseFormatter.success(duplicates));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async create(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const result = await tendersService.create(req.body, req.user!.id);
|
||||
res.status(201).json(
|
||||
ResponseFormatter.success(
|
||||
result,
|
||||
'تم إنشاء المناقصة بنجاح - Tender created successfully'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const page = parseInt(req.query.page as string) || 1;
|
||||
const pageSize = parseInt(req.query.pageSize as string) || 20;
|
||||
const filters = {
|
||||
search: req.query.search,
|
||||
status: req.query.status,
|
||||
source: req.query.source,
|
||||
announcementType: req.query.announcementType,
|
||||
};
|
||||
const result = await tendersService.findAll(filters, page, pageSize);
|
||||
res.json(
|
||||
ResponseFormatter.paginated(
|
||||
result.tenders,
|
||||
result.total,
|
||||
result.page,
|
||||
result.pageSize
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const tender = await tendersService.findById(req.params.id);
|
||||
res.json(ResponseFormatter.success(tender));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const tender = await tendersService.update(
|
||||
req.params.id,
|
||||
req.body,
|
||||
req.user!.id
|
||||
);
|
||||
res.json(
|
||||
ResponseFormatter.success(
|
||||
tender,
|
||||
'تم تحديث المناقصة بنجاح - Tender updated successfully'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async createDirective(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const directive = await tendersService.createDirective(
|
||||
req.params.id,
|
||||
req.body,
|
||||
req.user!.id
|
||||
);
|
||||
res.status(201).json(
|
||||
ResponseFormatter.success(
|
||||
directive,
|
||||
'تم إصدار التوجيه بنجاح - Directive created successfully'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateDirective(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const directive = await tendersService.updateDirective(
|
||||
req.params.directiveId,
|
||||
req.body,
|
||||
req.user!.id
|
||||
);
|
||||
res.json(
|
||||
ResponseFormatter.success(
|
||||
directive,
|
||||
'تم تحديث التوجيه بنجاح - Directive updated successfully'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getHistory(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const history = await tendersService.getHistory(req.params.id);
|
||||
res.json(ResponseFormatter.success(history));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async convertToDeal(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const deal = await tendersService.convertToDeal(
|
||||
req.params.id,
|
||||
{
|
||||
contactId: req.body.contactId,
|
||||
pipelineId: req.body.pipelineId,
|
||||
ownerId: req.body.ownerId,
|
||||
},
|
||||
req.user!.id
|
||||
);
|
||||
res.status(201).json(
|
||||
ResponseFormatter.success(
|
||||
deal,
|
||||
'تم تحويل المناقصة إلى فرصة بنجاح - Tender converted to deal successfully'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getSourceValues(_req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const values = tendersService.getSourceValues();
|
||||
res.json(ResponseFormatter.success(values));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getAnnouncementTypeValues(
|
||||
_req: AuthRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
const values = tendersService.getAnnouncementTypeValues();
|
||||
res.json(ResponseFormatter.success(values));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getDirectiveTypeValues(
|
||||
_req: AuthRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
const values = tendersService.getDirectiveTypeValues();
|
||||
res.json(ResponseFormatter.success(values));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadTenderAttachment(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json(
|
||||
ResponseFormatter.error('No file uploaded', 'Missing file')
|
||||
);
|
||||
}
|
||||
const attachment = await tendersService.uploadTenderAttachment(
|
||||
req.params.id,
|
||||
req.file,
|
||||
req.user!.id,
|
||||
(req.body.category as string) || undefined
|
||||
);
|
||||
res.status(201).json(
|
||||
ResponseFormatter.success(
|
||||
attachment,
|
||||
'تم رفع الملف بنجاح - File uploaded successfully'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadDirectiveAttachment(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json(
|
||||
ResponseFormatter.error('No file uploaded', 'Missing file')
|
||||
);
|
||||
}
|
||||
const attachment = await tendersService.uploadDirectiveAttachment(
|
||||
req.params.directiveId,
|
||||
req.file,
|
||||
req.user!.id,
|
||||
(req.body.category as string) || undefined
|
||||
);
|
||||
res.status(201).json(
|
||||
ResponseFormatter.success(
|
||||
attachment,
|
||||
'تم رفع الملف بنجاح - File uploaded successfully'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const tendersController = new TendersController();
|
||||
Reference in New Issue
Block a user