Initial commit: ZiRA Classification Tool for Zuyderland CMDB

This commit is contained in:
2026-01-06 15:32:28 +01:00
commit 0b27adc2fb
55 changed files with 24310 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
import { Router, Request, Response } from 'express';
import { dataService } from '../services/dataService.js';
import { databaseService } from '../services/database.js';
import { logger } from '../services/logger.js';
import { calculateRequiredEffortApplicationManagement, calculateRequiredEffortApplicationManagementWithBreakdown } from '../services/effortCalculation.js';
import type { SearchFilters, ApplicationUpdateRequest, ReferenceValue, ClassificationResult, ApplicationDetails, ApplicationStatus } from '../types/index.js';
const router = Router();
// Search applications with filters
router.post('/search', async (req: Request, res: Response) => {
try {
const { filters, page = 1, pageSize = 25 } = req.body as {
filters: SearchFilters;
page?: number;
pageSize?: number;
};
const result = await dataService.searchApplications(filters, page, pageSize);
res.json(result);
} catch (error) {
logger.error('Failed to search applications', error);
res.status(500).json({ error: 'Failed to search applications' });
}
});
// Get team dashboard data
router.get('/team-dashboard', async (req: Request, res: Response) => {
try {
const excludedStatusesParam = req.query.excludedStatuses as string | undefined;
let excludedStatuses: ApplicationStatus[] = [];
if (excludedStatusesParam && excludedStatusesParam.trim().length > 0) {
// Parse comma-separated statuses
excludedStatuses = excludedStatusesParam
.split(',')
.map(s => s.trim())
.filter(s => s.length > 0) as ApplicationStatus[];
} else {
// Default to excluding 'Closed' and 'Deprecated' if not specified
excludedStatuses = ['Closed', 'Deprecated'];
}
const data = await dataService.getTeamDashboardData(excludedStatuses);
res.json(data);
} catch (error) {
logger.error('Failed to get team dashboard data', error);
res.status(500).json({ error: 'Failed to get team dashboard data' });
}
});
// Get application by ID
router.get('/:id', async (req: Request, res: Response) => {
try {
const { id } = req.params;
// Don't treat special routes as application IDs
if (id === 'team-dashboard' || id === 'calculate-effort' || id === 'search') {
res.status(404).json({ error: 'Route not found' });
return;
}
const application = await dataService.getApplicationById(id);
if (!application) {
res.status(404).json({ error: 'Application not found' });
return;
}
res.json(application);
} catch (error) {
logger.error('Failed to get application', error);
res.status(500).json({ error: 'Failed to get application' });
}
});
// Update application
router.put('/:id', async (req: Request, res: Response) => {
try {
const { id } = req.params;
const updates = req.body as {
applicationFunctions?: ReferenceValue[];
dynamicsFactor?: ReferenceValue;
complexityFactor?: ReferenceValue;
numberOfUsers?: ReferenceValue;
governanceModel?: ReferenceValue;
source?: 'AI_ACCEPTED' | 'AI_MODIFIED' | 'MANUAL';
};
const application = await dataService.getApplicationById(id);
if (!application) {
res.status(404).json({ error: 'Application not found' });
return;
}
// Build changes object for history
const changes: ClassificationResult['changes'] = {};
if (updates.applicationFunctions) {
changes.applicationFunctions = {
from: application.applicationFunctions,
to: updates.applicationFunctions,
};
}
if (updates.dynamicsFactor) {
changes.dynamicsFactor = {
from: application.dynamicsFactor,
to: updates.dynamicsFactor,
};
}
if (updates.complexityFactor) {
changes.complexityFactor = {
from: application.complexityFactor,
to: updates.complexityFactor,
};
}
if (updates.numberOfUsers) {
changes.numberOfUsers = {
from: application.numberOfUsers,
to: updates.numberOfUsers,
};
}
if (updates.governanceModel) {
changes.governanceModel = {
from: application.governanceModel,
to: updates.governanceModel,
};
}
const success = await dataService.updateApplication(id, updates);
if (success) {
// Save to classification history
const classificationResult: ClassificationResult = {
applicationId: id,
applicationName: application.name,
changes,
source: updates.source || 'MANUAL',
timestamp: new Date(),
};
databaseService.saveClassificationResult(classificationResult);
const updatedApp = await dataService.getApplicationById(id);
res.json(updatedApp);
} else {
res.status(500).json({ error: 'Failed to update application' });
}
} catch (error) {
logger.error('Failed to update application', error);
res.status(500).json({ error: 'Failed to update application' });
}
});
// Calculate FTE effort for an application (real-time calculation without saving)
router.post('/calculate-effort', async (req: Request, res: Response) => {
try {
const applicationData = req.body as Partial<ApplicationDetails>;
// Build a complete ApplicationDetails object with defaults
const application: ApplicationDetails = {
id: applicationData.id || '',
key: applicationData.key || '',
name: applicationData.name || '',
searchReference: applicationData.searchReference || null,
description: applicationData.description || null,
supplierProduct: applicationData.supplierProduct || null,
organisation: applicationData.organisation || null,
hostingType: applicationData.hostingType || null,
status: applicationData.status || null,
businessImportance: applicationData.businessImportance || null,
businessImpactAnalyse: applicationData.businessImpactAnalyse || null,
systemOwner: applicationData.systemOwner || null,
businessOwner: applicationData.businessOwner || null,
functionalApplicationManagement: applicationData.functionalApplicationManagement || null,
technicalApplicationManagement: applicationData.technicalApplicationManagement || null,
technicalApplicationManagementPrimary: applicationData.technicalApplicationManagementPrimary || null,
technicalApplicationManagementSecondary: applicationData.technicalApplicationManagementSecondary || null,
medischeTechniek: applicationData.medischeTechniek || false,
applicationFunctions: applicationData.applicationFunctions || [],
dynamicsFactor: applicationData.dynamicsFactor || null,
complexityFactor: applicationData.complexityFactor || null,
numberOfUsers: applicationData.numberOfUsers || null,
governanceModel: applicationData.governanceModel || null,
applicationCluster: applicationData.applicationCluster || null,
applicationType: applicationData.applicationType || null,
platform: applicationData.platform || null,
requiredEffortApplicationManagement: null,
overrideFTE: applicationData.overrideFTE || null,
applicationManagementHosting: applicationData.applicationManagementHosting || null,
applicationManagementTAM: applicationData.applicationManagementTAM || null,
technischeArchitectuur: applicationData.technischeArchitectuur || null,
};
const result = calculateRequiredEffortApplicationManagementWithBreakdown(application);
res.json({
requiredEffortApplicationManagement: result.finalEffort,
breakdown: result.breakdown,
});
} catch (error) {
logger.error('Failed to calculate effort', error);
res.status(500).json({ error: 'Failed to calculate effort' });
}
});
// Get application classification history
router.get('/:id/history', async (req: Request, res: Response) => {
try {
const { id } = req.params;
const history = databaseService.getClassificationsByApplicationId(id);
res.json(history);
} catch (error) {
logger.error('Failed to get classification history', error);
res.status(500).json({ error: 'Failed to get classification history' });
}
});
export default router;