adpa-enterprise-framework-automation
Version:
Modular, standards-compliant Node.js/TypeScript automation framework for enterprise requirements, project, and data management. Provides CLI and API for BABOK v3, PMBOK 7th Edition, and DMBOK 2.0 (in progress). Production-ready Express.js API with TypeSpe
431 lines • 17 kB
JavaScript
import { ReviewService } from '../../services/ReviewService.js';
import { logger } from '../../utils/logger.js';
export class ReviewController {
static reviewService = new ReviewService();
/**
* Create a new document review
*/
static async createReview(req, res, next) {
try {
const request = req.body;
// Validate required fields
if (!request.documentId || !request.documentName || !request.documentType || !request.projectId) {
return res.status(400).json({
error: 'Missing required fields: documentId, documentName, documentType, projectId'
});
}
const review = await ReviewController.reviewService.createReview(request);
const response = {
review,
permissions: {
canEdit: true, // Would be determined by user permissions
canAssign: true,
canApprove: true,
canReject: true,
canEscalate: true,
canViewFeedback: true,
canAddFeedback: true
}
};
logger.info(`Review created: ${review.id}`);
res.status(201).json(response);
}
catch (error) {
logger.error('Error creating review:', error);
next(error);
}
}
/**
* Get a review by ID
*/
static async getReview(req, res, next) {
try {
const { reviewId } = req.params;
if (!reviewId) {
return res.status(400).json({ error: 'Review ID is required' });
}
const review = await ReviewController.reviewService.getReview(reviewId);
if (!review) {
return res.status(404).json({ error: 'Review not found' });
}
const response = {
review,
permissions: {
canEdit: true, // Would be determined by user permissions
canAssign: true,
canApprove: true,
canReject: true,
canEscalate: true,
canViewFeedback: true,
canAddFeedback: true
}
};
res.json(response);
}
catch (error) {
logger.error('Error getting review:', error);
next(error);
}
}
/**
* Search reviews with filters
*/
static async searchReviews(req, res, next) {
try {
const params = {
status: req.query.status ? (Array.isArray(req.query.status) ? req.query.status : [req.query.status]) : undefined,
priority: req.query.priority ? (Array.isArray(req.query.priority) ? req.query.priority : [req.query.priority]) : undefined,
documentType: req.query.documentType ? (Array.isArray(req.query.documentType) ? req.query.documentType : [req.query.documentType]) : undefined,
projectId: req.query.projectId,
reviewerId: req.query.reviewerId,
dateFrom: req.query.dateFrom ? new Date(req.query.dateFrom) : undefined,
dateTo: req.query.dateTo ? new Date(req.query.dateTo) : undefined,
limit: req.query.limit ? parseInt(req.query.limit) : 20,
offset: req.query.offset ? parseInt(req.query.offset) : 0,
sortBy: req.query.sortBy || 'createdAt',
sortOrder: req.query.sortOrder || 'desc'
};
const { reviews, total } = await ReviewController.reviewService.searchReviews(params);
const response = {
reviews,
total,
page: Math.floor((params.offset || 0) / (params.limit || 20)) + 1,
limit: params.limit || 20,
hasMore: (params.offset || 0) + reviews.length < total
};
res.json(response);
}
catch (error) {
logger.error('Error searching reviews:', error);
next(error);
}
}
/**
* Assign a reviewer to a review
*/
static async assignReviewer(req, res, next) {
try {
const { reviewId } = req.params;
const request = {
reviewId,
...req.body
};
// Validate required fields
if (!request.reviewerId || !request.role) {
return res.status(400).json({
error: 'Missing required fields: reviewerId, role'
});
}
const review = await ReviewController.reviewService.assignReviewer(request);
const response = {
review,
permissions: {
canEdit: true,
canAssign: true,
canApprove: true,
canReject: true,
canEscalate: true,
canViewFeedback: true,
canAddFeedback: true
}
};
logger.info(`Reviewer assigned: ${request.reviewerId} to review ${reviewId}`);
res.json(response);
}
catch (error) {
logger.error('Error assigning reviewer:', error);
next(error);
}
}
/**
* Submit feedback for a review
*/
static async submitFeedback(req, res, next) {
try {
const { reviewId } = req.params;
const request = {
reviewId,
...req.body
};
// Validate required fields
if (!request.roundNumber || !request.feedback || !request.decision) {
return res.status(400).json({
error: 'Missing required fields: roundNumber, feedback, decision'
});
}
const review = await ReviewController.reviewService.submitFeedback(request);
const response = {
review,
permissions: {
canEdit: true,
canAssign: true,
canApprove: true,
canReject: true,
canEscalate: true,
canViewFeedback: true,
canAddFeedback: true
}
};
logger.info(`Feedback submitted for review ${reviewId}, round ${request.roundNumber}`);
res.json(response);
}
catch (error) {
logger.error('Error submitting feedback:', error);
next(error);
}
}
/**
* Update review status
*/
static async updateReviewStatus(req, res, next) {
try {
const { reviewId } = req.params;
const request = {
reviewId,
...req.body
};
// Validate required fields
if (!request.status) {
return res.status(400).json({
error: 'Missing required field: status'
});
}
const review = await ReviewController.reviewService.updateReviewStatus(request);
const response = {
review,
permissions: {
canEdit: true,
canAssign: true,
canApprove: true,
canReject: true,
canEscalate: true,
canViewFeedback: true,
canAddFeedback: true
}
};
logger.info(`Review status updated: ${reviewId} to ${request.status}`);
res.json(response);
}
catch (error) {
logger.error('Error updating review status:', error);
next(error);
}
}
/**
* Get reviewer dashboard
*/
static async getReviewerDashboard(req, res, next) {
try {
const { reviewerId } = req.params;
if (!reviewerId) {
return res.status(400).json({ error: 'Reviewer ID is required' });
}
const dashboard = await ReviewController.reviewService.getReviewerDashboard(reviewerId);
res.json(dashboard);
}
catch (error) {
logger.error('Error getting reviewer dashboard:', error);
next(error);
}
}
/**
* Get review analytics
*/
static async getReviewAnalytics(req, res, next) {
try {
const startDate = req.query.startDate
? new Date(req.query.startDate)
: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // Default to last 30 days
const endDate = req.query.endDate
? new Date(req.query.endDate)
: new Date(); // Default to now
const analytics = await ReviewController.reviewService.getReviewAnalytics(startDate, endDate);
const response = {
analytics,
generatedAt: new Date()
};
res.json(response);
}
catch (error) {
logger.error('Error getting review analytics:', error);
next(error);
}
}
/**
* Get reviews for a specific project
*/
static async getProjectReviews(req, res, next) {
try {
const { projectId } = req.params;
if (!projectId) {
return res.status(400).json({ error: 'Project ID is required' });
}
const params = {
projectId,
limit: req.query.limit ? parseInt(req.query.limit) : 50,
offset: req.query.offset ? parseInt(req.query.offset) : 0,
sortBy: 'createdAt',
sortOrder: 'desc'
};
const { reviews, total } = await ReviewController.reviewService.searchReviews(params);
const response = {
reviews,
total,
page: Math.floor((params.offset || 0) / (params.limit || 50)) + 1,
limit: params.limit || 50,
hasMore: (params.offset || 0) + reviews.length < total
};
res.json(response);
}
catch (error) {
logger.error('Error getting project reviews:', error);
next(error);
}
}
/**
* Get reviews assigned to current user
*/
static async getMyReviews(req, res, next) {
try {
// In a real implementation, you would get the user ID from the authenticated session
const userId = req.headers['x-user-id'] || req.query.userId;
if (!userId) {
return res.status(400).json({ error: 'User ID is required' });
}
const params = {
reviewerId: userId,
status: req.query.status ? (Array.isArray(req.query.status) ? req.query.status : [req.query.status]) : undefined,
limit: req.query.limit ? parseInt(req.query.limit) : 20,
offset: req.query.offset ? parseInt(req.query.offset) : 0,
sortBy: 'dueDate',
sortOrder: 'asc'
};
const { reviews, total } = await ReviewController.reviewService.searchReviews(params);
const response = {
reviews,
total,
page: Math.floor((params.offset || 0) / (params.limit || 20)) + 1,
limit: params.limit || 20,
hasMore: (params.offset || 0) + reviews.length < total
};
res.json(response);
}
catch (error) {
logger.error('Error getting my reviews:', error);
next(error);
}
}
/**
* Accept a review assignment
*/
static async acceptReviewAssignment(req, res, next) {
try {
const { reviewId } = req.params;
const userId = req.headers['x-user-id'] || req.body.userId;
if (!userId) {
return res.status(400).json({ error: 'User ID is required' });
}
const review = await ReviewController.reviewService.getReview(reviewId);
if (!review) {
return res.status(404).json({ error: 'Review not found' });
}
// Find the reviewer assignment
const assignment = review.assignedReviewers.find(r => r.reviewerId === userId);
if (!assignment) {
return res.status(404).json({ error: 'Review assignment not found for this user' });
}
if (assignment.status !== 'assigned') {
return res.status(400).json({ error: 'Review assignment cannot be accepted in current status' });
}
// Update the review to mark assignment as accepted
const updatedReview = await ReviewController.reviewService.updateReviewStatus({
reviewId,
status: 'in_review',
comments: 'Reviewer accepted the assignment'
});
logger.info(`Review assignment accepted: ${reviewId} by ${userId}`);
res.json({ review: updatedReview, message: 'Review assignment accepted successfully' });
}
catch (error) {
logger.error('Error accepting review assignment:', error);
next(error);
}
}
/**
* Decline a review assignment
*/
static async declineReviewAssignment(req, res, next) {
try {
const { reviewId } = req.params;
const userId = req.headers['x-user-id'] || req.body.userId;
const reason = req.body.reason || 'No reason provided';
if (!userId) {
return res.status(400).json({ error: 'User ID is required' });
}
const review = await ReviewController.reviewService.getReview(reviewId);
if (!review) {
return res.status(404).json({ error: 'Review not found' });
}
// Find the reviewer assignment
const assignment = review.assignedReviewers.find(r => r.reviewerId === userId);
if (!assignment) {
return res.status(404).json({ error: 'Review assignment not found for this user' });
}
if (assignment.status !== 'assigned') {
return res.status(400).json({ error: 'Review assignment cannot be declined in current status' });
}
// In a real implementation, you would:
// 1. Mark the assignment as declined
// 2. Find a replacement reviewer
// 3. Notify the review coordinator
logger.info(`Review assignment declined: ${reviewId} by ${userId}, reason: ${reason}`);
res.json({ message: 'Review assignment declined successfully' });
}
catch (error) {
logger.error('Error declining review assignment:', error);
next(error);
}
}
/**
* Get review statistics summary
*/
static async getReviewStats(req, res, next) {
try {
const projectId = req.query.projectId;
const reviewerId = req.query.reviewerId;
const params = {
projectId,
reviewerId,
limit: 1000, // Get all for stats calculation
offset: 0
};
const { reviews } = await ReviewController.reviewService.searchReviews(params);
// Calculate statistics
const stats = {
total: reviews.length,
pending: reviews.filter(r => ['pending_assignment', 'assigned', 'in_review'].includes(r.status)).length,
completed: reviews.filter(r => ['approved', 'rejected', 'completed'].includes(r.status)).length,
overdue: reviews.filter(r => r.dueDate && new Date() > r.dueDate &&
!['approved', 'rejected', 'completed'].includes(r.status)).length,
byStatus: reviews.reduce((acc, review) => {
acc[review.status] = (acc[review.status] || 0) + 1;
return acc;
}, {}),
byPriority: reviews.reduce((acc, review) => {
acc[review.priority] = (acc[review.priority] || 0) + 1;
return acc;
}, {}),
byDocumentType: reviews.reduce((acc, review) => {
acc[review.documentType] = (acc[review.documentType] || 0) + 1;
return acc;
}, {})
};
res.json(stats);
}
catch (error) {
logger.error('Error getting review stats:', error);
next(error);
}
}
}
//# sourceMappingURL=ReviewController.js.map