Fix : probleme de la détection d'utilisation par un autre événement
This commit is contained in:
@@ -1424,6 +1424,49 @@ exports.getAlerts = onRequest(httpOptions, withCors(async (req, res) => {
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
exports.markAlertAsRead = onRequest(httpOptions, withCors(async (req, res) => {
|
||||
try {
|
||||
await auth.authenticateUser(req);
|
||||
|
||||
const alertId = req.body.data?.alertId;
|
||||
if (!alertId) {
|
||||
res.status(400).json({ error: 'alertId is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
await db.collection('alerts').doc(alertId).update({
|
||||
isRead: true
|
||||
});
|
||||
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error("Error marking alert as read:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
exports.deleteAlert = onRequest(httpOptions, withCors(async (req, res) => {
|
||||
try {
|
||||
await auth.authenticateUser(req);
|
||||
|
||||
const alertId = req.body.data?.alertId;
|
||||
if (!alertId) {
|
||||
res.status(400).json({ error: 'alertId is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
await db.collection('alerts').doc(alertId).delete();
|
||||
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error("Error deleting alert:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// USERS - Read with permissions
|
||||
// ============================================================================
|
||||
@@ -1673,3 +1716,503 @@ exports.updateUser = onCall(async (request) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// EQUIPMENT AVAILABILITY - Vérification de disponibilité
|
||||
// ============================================================================
|
||||
|
||||
exports.checkEquipmentAvailability = onRequest(httpOptions, withCors(async (req, res) => {
|
||||
try {
|
||||
const decodedToken = await auth.authenticateUser(req);
|
||||
const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');
|
||||
|
||||
if (!hasAccess) {
|
||||
res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { equipmentId, startDate, endDate, excludeEventId } = req.body.data;
|
||||
|
||||
if (!equipmentId || !startDate || !endDate) {
|
||||
res.status(400).json({ error: 'equipmentId, startDate, and endDate are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Checking availability for equipment ${equipmentId} from ${startDate} to ${endDate}, excluding event: ${excludeEventId}`);
|
||||
|
||||
const startTimestamp = admin.firestore.Timestamp.fromDate(new Date(startDate));
|
||||
const endTimestamp = admin.firestore.Timestamp.fromDate(new Date(endDate));
|
||||
|
||||
const eventsSnapshot = await db.collection('events')
|
||||
.where('status', '!=', 'CANCELLED')
|
||||
.get();
|
||||
|
||||
logger.info(`Found ${eventsSnapshot.docs.length} events to check`);
|
||||
|
||||
const conflicts = [];
|
||||
|
||||
for (const eventDoc of eventsSnapshot.docs) {
|
||||
const event = eventDoc.data();
|
||||
|
||||
if (excludeEventId && eventDoc.id === excludeEventId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gérer les dates qui peuvent être des Timestamps ou des objets Date
|
||||
let eventStart, eventEnd;
|
||||
if (event.StartDateTime) {
|
||||
eventStart = event.StartDateTime.toDate ? event.StartDateTime.toDate() : new Date(event.StartDateTime);
|
||||
}
|
||||
if (event.EndDateTime) {
|
||||
eventEnd = event.EndDateTime.toDate ? event.EndDateTime.toDate() : new Date(event.EndDateTime);
|
||||
}
|
||||
|
||||
|
||||
if (!eventStart || !eventEnd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vérifier si l'équipement est assigné à cet événement (directement ou via une boîte)
|
||||
const assignedEquipment = event.assignedEquipment || [];
|
||||
const assignedContainers = event.assignedContainers || [];
|
||||
|
||||
// Vérifier si l'équipement est directement assigné
|
||||
const isEquipmentDirectlyAssigned = assignedEquipment.some(eq => eq.equipmentId === equipmentId);
|
||||
|
||||
// Vérifier si l'équipement est dans une boîte assignée
|
||||
let isEquipmentInAssignedContainer = false;
|
||||
if (assignedContainers.length > 0) {
|
||||
logger.info(`Event ${eventDoc.id} has ${assignedContainers.length} assigned containers`);
|
||||
// Récupérer les conteneurs assignés et vérifier si l'équipement y est
|
||||
for (const containerId of assignedContainers) {
|
||||
const containerDoc = await db.collection('containers').doc(containerId).get();
|
||||
if (containerDoc.exists) {
|
||||
const containerData = containerDoc.data();
|
||||
const equipmentIds = containerData.equipmentIds || [];
|
||||
logger.info(`Container ${containerId} contains equipment IDs: ${equipmentIds.join(', ')}`);
|
||||
if (equipmentIds.includes(equipmentId)) {
|
||||
isEquipmentInAssignedContainer = true;
|
||||
logger.info(`Equipment ${equipmentId} found in container ${containerId} for event ${eventDoc.id}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isEquipmentDirectlyAssigned) {
|
||||
logger.info(`Equipment ${equipmentId} is directly assigned to event ${eventDoc.id}`);
|
||||
}
|
||||
|
||||
if (!isEquipmentDirectlyAssigned && !isEquipmentInAssignedContainer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vérifier le chevauchement de dates
|
||||
const requestStart = startTimestamp.toDate();
|
||||
const requestEnd = endTimestamp.toDate();
|
||||
|
||||
// Inclure les temps d'installation et de démontage
|
||||
const installationTime = event.InstallationTime || 0;
|
||||
const disassemblyTime = event.DisassemblyTime || 0;
|
||||
|
||||
const eventStartWithSetup = new Date(eventStart);
|
||||
eventStartWithSetup.setHours(eventStartWithSetup.getHours() - installationTime);
|
||||
|
||||
const eventEndWithTeardown = new Date(eventEnd);
|
||||
eventEndWithTeardown.setHours(eventEndWithTeardown.getHours() + disassemblyTime);
|
||||
|
||||
// Il y a conflit si les périodes se chevauchent
|
||||
const hasOverlap = requestStart < eventEndWithTeardown && requestEnd > eventStartWithSetup;
|
||||
|
||||
if (hasOverlap) {
|
||||
// Calculer les jours de chevauchement
|
||||
const overlapStart = new Date(Math.max(requestStart, eventStartWithSetup));
|
||||
const overlapEnd = new Date(Math.min(requestEnd, eventEndWithTeardown));
|
||||
const overlapDays = Math.ceil((overlapEnd - overlapStart) / (1000 * 60 * 60 * 24));
|
||||
|
||||
logger.info(`Conflict detected: Equipment ${equipmentId} conflicts with event ${eventDoc.id} (${event.Name})`);
|
||||
|
||||
// Retourner les détails complets de l'événement
|
||||
const eventData = helpers.serializeTimestamps(event);
|
||||
conflicts.push({
|
||||
eventId: eventDoc.id,
|
||||
eventName: event.Name,
|
||||
eventData: eventData, // Ajouter toutes les données de l'événement
|
||||
startDate: eventStart.toISOString(),
|
||||
endDate: eventEnd.toISOString(),
|
||||
overlapDays: overlapDays
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Total conflicts found: ${conflicts.length}`);
|
||||
|
||||
res.status(200).json({ conflicts, available: conflicts.length === 0 });
|
||||
} catch (error) {
|
||||
logger.error("Error checking equipment availability:", error);
|
||||
res.status(500).json({ error: error.message || "Failed to check equipment availability" });
|
||||
}
|
||||
}));
|
||||
|
||||
exports.checkContainerAvailability = onRequest(httpOptions, withCors(async (req, res) => {
|
||||
try {
|
||||
const decodedToken = await auth.authenticateUser(req);
|
||||
const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');
|
||||
|
||||
if (!hasAccess) {
|
||||
res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { containerId, startDate, endDate, excludeEventId } = req.body.data;
|
||||
|
||||
if (!containerId || !startDate || !endDate) {
|
||||
res.status(400).json({ error: 'containerId, startDate, and endDate are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupérer le container et ses équipements
|
||||
const containerDoc = await db.collection('containers').doc(containerId).get();
|
||||
if (!containerDoc.exists) {
|
||||
throw new Error('Container not found');
|
||||
}
|
||||
|
||||
const containerData = containerDoc.data();
|
||||
const equipmentIds = containerData.equipmentIds || [];
|
||||
|
||||
const startTimestamp = admin.firestore.Timestamp.fromDate(new Date(startDate));
|
||||
const endTimestamp = admin.firestore.Timestamp.fromDate(new Date(endDate));
|
||||
|
||||
const eventsSnapshot = await db.collection('events')
|
||||
.where('status', '!=', 'CANCELLED')
|
||||
.get();
|
||||
|
||||
const containerConflicts = [];
|
||||
const equipmentConflicts = {};
|
||||
|
||||
for (const eventDoc of eventsSnapshot.docs) {
|
||||
const event = eventDoc.data();
|
||||
|
||||
if (excludeEventId && eventDoc.id === excludeEventId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gérer les dates
|
||||
let eventStart, eventEnd;
|
||||
if (event.StartDateTime) {
|
||||
eventStart = event.StartDateTime.toDate ? event.StartDateTime.toDate() : new Date(event.StartDateTime);
|
||||
}
|
||||
if (event.EndDateTime) {
|
||||
eventEnd = event.EndDateTime.toDate ? event.EndDateTime.toDate() : new Date(event.EndDateTime);
|
||||
}
|
||||
|
||||
if (!eventStart || !eventEnd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vérifier si le container est assigné
|
||||
const assignedContainers = event.assignedContainers || [];
|
||||
const isContainerAssigned = assignedContainers.includes(containerId);
|
||||
|
||||
// Vérifier si des équipements du container sont assignés
|
||||
const assignedEquipment = event.assignedEquipment || [];
|
||||
const conflictingEquipmentIds = equipmentIds.filter(eqId =>
|
||||
assignedEquipment.some(eq => eq.equipmentId === eqId)
|
||||
);
|
||||
|
||||
if (!isContainerAssigned && conflictingEquipmentIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vérifier le chevauchement de dates
|
||||
const requestStart = startTimestamp.toDate();
|
||||
const requestEnd = endTimestamp.toDate();
|
||||
|
||||
const installationTime = event.InstallationTime || 0;
|
||||
const disassemblyTime = event.DisassemblyTime || 0;
|
||||
|
||||
const eventStartWithSetup = new Date(eventStart);
|
||||
eventStartWithSetup.setHours(eventStartWithSetup.getHours() - installationTime);
|
||||
|
||||
const eventEndWithTeardown = new Date(eventEnd);
|
||||
eventEndWithTeardown.setHours(eventEndWithTeardown.getHours() + disassemblyTime);
|
||||
|
||||
const hasOverlap = requestStart < eventEndWithTeardown && requestEnd > eventStartWithSetup;
|
||||
|
||||
if (hasOverlap) {
|
||||
const overlapStart = new Date(Math.max(requestStart, eventStartWithSetup));
|
||||
const overlapEnd = new Date(Math.min(requestEnd, eventEndWithTeardown));
|
||||
const overlapDays = Math.ceil((overlapEnd - overlapStart) / (1000 * 60 * 60 * 24));
|
||||
|
||||
const conflictInfo = {
|
||||
eventId: eventDoc.id,
|
||||
eventName: event.Name,
|
||||
startDate: eventStart.toISOString(),
|
||||
endDate: eventEnd.toISOString(),
|
||||
overlapDays: overlapDays
|
||||
};
|
||||
|
||||
if (isContainerAssigned) {
|
||||
containerConflicts.push(conflictInfo);
|
||||
}
|
||||
|
||||
conflictingEquipmentIds.forEach(eqId => {
|
||||
if (!equipmentConflicts[eqId]) {
|
||||
equipmentConflicts[eqId] = [];
|
||||
}
|
||||
equipmentConflicts[eqId].push(conflictInfo);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const hasContainerConflict = containerConflicts.length > 0;
|
||||
const hasPartialConflict = Object.keys(equipmentConflicts).length > 0 && !hasContainerConflict;
|
||||
const conflictType = hasContainerConflict ? 'complete' : (hasPartialConflict ? 'partial' : 'none');
|
||||
|
||||
res.status(200).json({
|
||||
conflictType,
|
||||
containerConflicts,
|
||||
equipmentConflicts,
|
||||
isAvailable: conflictType === 'none'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error checking container availability:", error);
|
||||
res.status(500).json({ error: error.message || "Failed to check container availability" });
|
||||
}
|
||||
}));
|
||||
|
||||
// ============================================================================
|
||||
// AVAILABILITY - Optimized batch check
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Récupère tous les équipements et conteneurs en conflit pour une période donnée
|
||||
* Optimisé : une seule requête au lieu d'une par équipement
|
||||
*/
|
||||
exports.getConflictingEquipmentIds = onRequest(httpOptions, withCors(async (req, res) => {
|
||||
try {
|
||||
const decodedToken = await auth.authenticateUser(req);
|
||||
const hasAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');
|
||||
|
||||
if (!hasAccess) {
|
||||
res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { startDate, endDate, excludeEventId, installationTime = 0, disassemblyTime = 0 } = req.body.data;
|
||||
|
||||
if (!startDate || !endDate) {
|
||||
res.status(400).json({ error: 'startDate and endDate are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Getting conflicting equipment IDs for period ${startDate} to ${endDate}`);
|
||||
|
||||
// Calculer la période effective avec temps de montage/démontage
|
||||
const requestStartDate = new Date(startDate);
|
||||
requestStartDate.setHours(requestStartDate.getHours() - installationTime);
|
||||
|
||||
const requestEndDate = new Date(endDate);
|
||||
requestEndDate.setHours(requestEndDate.getHours() + disassemblyTime);
|
||||
|
||||
// Récupérer tous les événements non annulés
|
||||
const eventsSnapshot = await db.collection('events')
|
||||
.where('status', '!=', 'CANCELLED')
|
||||
.get();
|
||||
|
||||
logger.info(`Found ${eventsSnapshot.docs.length} events to check`);
|
||||
|
||||
// Maps pour stocker les conflits
|
||||
const conflictingEquipmentIds = new Set();
|
||||
const conflictingContainerIds = new Set();
|
||||
const conflictDetails = {}; // { equipmentId/containerId: [{ eventId, eventName, startDate, endDate }] }
|
||||
|
||||
for (const eventDoc of eventsSnapshot.docs) {
|
||||
// Exclure l'événement en cours d'édition
|
||||
if (excludeEventId && eventDoc.id === excludeEventId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const event = eventDoc.data();
|
||||
|
||||
// Gérer les dates
|
||||
let eventStart, eventEnd;
|
||||
if (event.StartDateTime) {
|
||||
eventStart = event.StartDateTime.toDate ? event.StartDateTime.toDate() : new Date(event.StartDateTime);
|
||||
}
|
||||
if (event.EndDateTime) {
|
||||
eventEnd = event.EndDateTime.toDate ? event.EndDateTime.toDate() : new Date(event.EndDateTime);
|
||||
}
|
||||
|
||||
if (!eventStart || !eventEnd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ajouter temps de montage/démontage de cet événement
|
||||
const eventInstallTime = event.InstallationTime || 0;
|
||||
const eventDisassemblyTime = event.DisassemblyTime || 0;
|
||||
|
||||
const eventStartWithSetup = new Date(eventStart);
|
||||
eventStartWithSetup.setHours(eventStartWithSetup.getHours() - eventInstallTime);
|
||||
|
||||
const eventEndWithTeardown = new Date(eventEnd);
|
||||
eventEndWithTeardown.setHours(eventEndWithTeardown.getHours() + eventDisassemblyTime);
|
||||
|
||||
// Vérifier le chevauchement de dates
|
||||
const hasOverlap = requestStartDate < eventEndWithTeardown && requestEndDate > eventStartWithSetup;
|
||||
|
||||
if (!hasOverlap) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Il y a chevauchement ! Récupérer les équipements et conteneurs assignés
|
||||
const assignedEquipment = event.assignedEquipment || [];
|
||||
const assignedContainers = event.assignedContainers || [];
|
||||
|
||||
const conflictInfo = {
|
||||
eventId: eventDoc.id,
|
||||
eventName: event.Name,
|
||||
startDate: eventStart.toISOString(),
|
||||
endDate: eventEnd.toISOString(),
|
||||
};
|
||||
|
||||
// Ajouter les équipements directement assignés
|
||||
for (const eq of assignedEquipment) {
|
||||
const equipmentId = eq.equipmentId;
|
||||
conflictingEquipmentIds.add(equipmentId);
|
||||
|
||||
if (!conflictDetails[equipmentId]) {
|
||||
conflictDetails[equipmentId] = [];
|
||||
}
|
||||
conflictDetails[equipmentId].push(conflictInfo);
|
||||
}
|
||||
|
||||
// Ajouter les conteneurs assignés
|
||||
for (const containerId of assignedContainers) {
|
||||
conflictingContainerIds.add(containerId);
|
||||
|
||||
if (!conflictDetails[containerId]) {
|
||||
conflictDetails[containerId] = [];
|
||||
}
|
||||
conflictDetails[containerId].push(conflictInfo);
|
||||
|
||||
// Récupérer les équipements dans ce conteneur
|
||||
const containerDoc = await db.collection('containers').doc(containerId).get();
|
||||
if (containerDoc.exists) {
|
||||
const containerData = containerDoc.data();
|
||||
const equipmentIds = containerData.equipmentIds || [];
|
||||
|
||||
// Marquer tous les équipements du conteneur comme en conflit
|
||||
for (const equipmentId of equipmentIds) {
|
||||
conflictingEquipmentIds.add(equipmentId);
|
||||
|
||||
if (!conflictDetails[equipmentId]) {
|
||||
conflictDetails[equipmentId] = [];
|
||||
}
|
||||
// Ajouter une note indiquant que c'est via le conteneur
|
||||
conflictDetails[equipmentId].push({
|
||||
...conflictInfo,
|
||||
viaContainer: containerId,
|
||||
viaContainerName: containerData.name || 'Conteneur inconnu',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Found ${conflictingEquipmentIds.size} conflicting equipment(s) and ${conflictingContainerIds.size} conflicting container(s)`);
|
||||
|
||||
res.status(200).json({
|
||||
conflictingEquipmentIds: Array.from(conflictingEquipmentIds),
|
||||
conflictingContainerIds: Array.from(conflictingContainerIds),
|
||||
conflictDetails: conflictDetails,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error getting conflicting equipment IDs:", error);
|
||||
res.status(500).json({ error: error.message || "Failed to get conflicting equipment IDs" });
|
||||
}
|
||||
}));
|
||||
|
||||
// ============================================================================
|
||||
// USER - Get current authenticated user
|
||||
// ============================================================================
|
||||
exports.getCurrentUser = onRequest(httpOptions, withCors(async (req, res) => {
|
||||
try {
|
||||
const decodedToken = await auth.authenticateUser(req);
|
||||
const userId = decodedToken.uid;
|
||||
|
||||
const userDoc = await db.collection('users').doc(userId).get();
|
||||
if (!userDoc.exists) {
|
||||
res.status(404).json({ error: 'User not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const userData = userDoc.data();
|
||||
|
||||
// Récupérer le rôle
|
||||
let roleData = null;
|
||||
if (userData.role) {
|
||||
const roleDoc = await userData.role.get();
|
||||
if (roleDoc.exists) {
|
||||
roleData = { id: roleDoc.id, ...roleDoc.data() };
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
user: {
|
||||
uid: userId,
|
||||
...helpers.serializeTimestamps(userData),
|
||||
role: roleData
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error getting current user:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}));
|
||||
|
||||
// ============================================================================
|
||||
// MAINTENANCE - Delete
|
||||
// ============================================================================
|
||||
exports.deleteMaintenance = onRequest(httpOptions, withCors(async (req, res) => {
|
||||
try {
|
||||
const decodedToken = await auth.authenticateUser(req);
|
||||
|
||||
// Vérifier permission
|
||||
const canManage = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
|
||||
if (!canManage) {
|
||||
res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
|
||||
return;
|
||||
}
|
||||
|
||||
const maintenanceId = req.body.data?.maintenanceId;
|
||||
if (!maintenanceId) {
|
||||
res.status(400).json({ error: 'maintenanceId is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupérer la maintenance pour connaître les équipements
|
||||
const maintenanceDoc = await db.collection('maintenances').doc(maintenanceId).get();
|
||||
if (maintenanceDoc.exists) {
|
||||
const maintenance = maintenanceDoc.data();
|
||||
|
||||
// Retirer la maintenance des équipements
|
||||
if (maintenance.equipmentIds) {
|
||||
for (const equipmentId of maintenance.equipmentIds) {
|
||||
const equipmentDoc = await db.collection('equipments').doc(equipmentId).get();
|
||||
if (equipmentDoc.exists) {
|
||||
const equipmentData = equipmentDoc.data();
|
||||
const maintenanceIds = (equipmentData.maintenanceIds || []).filter(id => id !== maintenanceId);
|
||||
await db.collection('equipments').doc(equipmentId).update({ maintenanceIds });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await db.collection('maintenances').doc(maintenanceId).delete();
|
||||
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error("Error deleting maintenance:", error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user