Refactor member activity retrieval to include group activities and eliminate duplicates

This commit enhances the `getMemberActivities` and `getMemberLastParticipations` functions by introducing logic to gather group activities for members based on their group associations. It ensures that both explicitly assigned member activities and group activities are combined while filtering out duplicates. The overall structure of the activity retrieval process is improved for better clarity and efficiency, enhancing the accuracy of the data returned to the frontend.
This commit is contained in:
Torsten Schulz (local)
2025-11-14 23:07:02 +01:00
parent d08835e206
commit 28c92b66af

View File

@@ -49,13 +49,19 @@ export const getMemberActivities = async (req, res) => {
const participantIds = participants.map(p => p.id);
// Get all diary member activities for this member
const whereClause = {
participantId: participantIds
};
// Sammle alle Gruppen-IDs, zu denen der Member gehört
const memberGroupIds = new Set();
participants.forEach(p => {
if (p.groupId !== null && p.groupId !== undefined) {
memberGroupIds.add(p.groupId);
}
});
// 1. Get all diary member activities explicitly assigned to this member
const memberActivities = await DiaryMemberActivity.findAll({
where: whereClause,
where: {
participantId: participantIds
},
include: [
{
model: Participant,
@@ -90,28 +96,90 @@ export const getMemberActivities = async (req, res) => {
order: [[{ model: DiaryDateActivity, as: 'activity' }, { model: DiaryDates, as: 'diaryDate' }, 'date', 'DESC']]
});
// Group activities by name and count occurrences, considering group assignment
// 2. Get all group activities for groups the member belongs to
const groupActivities = [];
if (memberGroupIds.size > 0) {
const groupActivitiesData = await DiaryDateActivity.findAll({
include: [
{
model: DiaryDates,
as: 'diaryDate',
where: startDate ? {
date: {
[Op.gte]: startDate
}
} : {}
},
{
model: PredefinedActivity,
as: 'predefinedActivity',
required: true
},
{
model: GroupActivity,
as: 'groupActivities',
where: {
groupId: {
[Op.in]: Array.from(memberGroupIds)
}
},
required: true
}
]
});
// Erstelle virtuelle DiaryMemberActivity-Objekte für Gruppen-Aktivitäten
for (const activity of groupActivitiesData) {
// Finde den entsprechenden Participant für diese Aktivität
const diaryDateId = activity.diaryDateId;
const relevantParticipants = participants.filter(p => p.diaryDateId === diaryDateId);
for (const participant of relevantParticipants) {
// Prüfe, ob die Aktivität für die Gruppe des Participants ist
const activityGroupIds = activity.groupActivities?.map(ga => ga.groupId) || [];
if (participant.groupId !== null && activityGroupIds.includes(participant.groupId)) {
// Erstelle ein virtuelles DiaryMemberActivity-Objekt
groupActivities.push({
activity: activity,
participant: participant,
id: null // Virtuell, nicht in DB
});
}
}
}
}
// 3. Kombiniere beide Listen und entferne Duplikate
// Ein Duplikat liegt vor, wenn dieselbe Aktivität für denselben Participant bereits explizit zugeordnet ist
const explicitActivityKeys = new Set();
memberActivities.forEach(ma => {
if (ma.activity && ma.activity.id && ma.participant && ma.participant.id) {
// Erstelle einen eindeutigen Schlüssel: activityId-participantId
const key = `${ma.activity.id}-${ma.participant.id}`;
explicitActivityKeys.add(key);
}
});
// Filtere Gruppen-Aktivitäten, die bereits explizit zugeordnet sind
const uniqueGroupActivities = groupActivities.filter(ga => {
if (!ga.activity || !ga.activity.id || !ga.participant || !ga.participant.id) {
return false;
}
const key = `${ga.activity.id}-${ga.participant.id}`;
return !explicitActivityKeys.has(key);
});
// Kombiniere beide Listen
const allActivities = [...memberActivities, ...uniqueGroupActivities];
// Group activities by name and count occurrences
const activityMap = new Map();
for (const ma of memberActivities) {
for (const ma of allActivities) {
if (!ma.activity || !ma.activity.predefinedActivity || !ma.participant) {
continue;
}
// Check group assignment
const participantGroupId = ma.participant.groupId;
const activityGroupIds = ma.activity.groupActivities?.map(ga => ga.groupId) || [];
// Filter: Only count if:
// 1. Activity has no group assignment (empty activityGroupIds) - activity is for all groups OR
// 2. Participant's group matches one of the activity's groups
const shouldCount = activityGroupIds.length === 0 ||
(participantGroupId !== null && activityGroupIds.includes(participantGroupId));
if (!shouldCount) {
continue;
}
const activity = ma.activity.predefinedActivity;
const activityName = activity.name;
const date = ma.activity.diaryDate?.date;
@@ -162,7 +230,15 @@ export const getMemberLastParticipations = async (req, res) => {
const participantIds = participants.map(p => p.id);
// Get last participations for this member
// Sammle alle Gruppen-IDs, zu denen der Member gehört
const memberGroupIds = new Set();
participants.forEach(p => {
if (p.groupId !== null && p.groupId !== undefined) {
memberGroupIds.add(p.groupId);
}
});
// 1. Get last participations explicitly assigned to this member
const memberActivities = await DiaryMemberActivity.findAll({
where: {
participantId: participantIds
@@ -197,22 +273,92 @@ export const getMemberLastParticipations = async (req, res) => {
limit: parseInt(limit) * 10 // Get more to filter by group
});
// Format the results, considering group assignment
const participations = memberActivities
// 2. Get all group activities for groups the member belongs to
const groupActivities = [];
if (memberGroupIds.size > 0) {
const groupActivitiesData = await DiaryDateActivity.findAll({
include: [
{
model: DiaryDates,
as: 'diaryDate'
},
{
model: PredefinedActivity,
as: 'predefinedActivity',
required: true
},
{
model: GroupActivity,
as: 'groupActivities',
where: {
groupId: {
[Op.in]: Array.from(memberGroupIds)
}
},
required: true
}
],
order: [[{ model: DiaryDates, as: 'diaryDate' }, 'date', 'DESC']],
limit: parseInt(limit) * 10 // Get more to filter
});
// Erstelle virtuelle DiaryMemberActivity-Objekte für Gruppen-Aktivitäten
for (const activity of groupActivitiesData) {
// Finde den entsprechenden Participant für diese Aktivität
const diaryDateId = activity.diaryDateId;
const relevantParticipants = participants.filter(p => p.diaryDateId === diaryDateId);
for (const participant of relevantParticipants) {
// Prüfe, ob die Aktivität für die Gruppe des Participants ist
const activityGroupIds = activity.groupActivities?.map(ga => ga.groupId) || [];
if (participant.groupId !== null && activityGroupIds.includes(participant.groupId)) {
// Erstelle ein virtuelles DiaryMemberActivity-Objekt
groupActivities.push({
activity: activity,
participant: participant,
id: null // Virtuell, nicht in DB
});
}
}
}
}
// 3. Kombiniere beide Listen und entferne Duplikate
// Ein Duplikat liegt vor, wenn dieselbe Aktivität für denselben Participant bereits explizit zugeordnet ist
const explicitActivityKeys = new Set();
memberActivities.forEach(ma => {
if (ma.activity && ma.activity.id && ma.participant && ma.participant.id) {
// Erstelle einen eindeutigen Schlüssel: activityId-participantId
const key = `${ma.activity.id}-${ma.participant.id}`;
explicitActivityKeys.add(key);
}
});
// Filtere Gruppen-Aktivitäten, die bereits explizit zugeordnet sind
const uniqueGroupActivities = groupActivities.filter(ga => {
if (!ga.activity || !ga.activity.id || !ga.participant || !ga.participant.id) {
return false;
}
const key = `${ga.activity.id}-${ga.participant.id}`;
return !explicitActivityKeys.has(key);
});
// Kombiniere beide Listen
const allActivities = [...memberActivities, ...uniqueGroupActivities];
// Format the results
const participations = allActivities
.filter(ma => {
if (!ma.activity || !ma.activity.predefinedActivity || !ma.activity.diaryDate || !ma.participant) {
return false;
}
// Check group assignment
const participantGroupId = ma.participant.groupId;
const activityGroupIds = ma.activity.groupActivities?.map(ga => ga.groupId) || [];
// Filter: Only count if:
// 1. Activity has no group assignment (empty activityGroupIds) - activity is for all groups OR
// 2. Participant's group matches one of the activity's groups
return activityGroupIds.length === 0 ||
(participantGroupId !== null && activityGroupIds.includes(participantGroupId));
return true;
})
.sort((a, b) => {
// Sortiere nach Datum (neueste zuerst)
const dateA = new Date(a.activity.diaryDate.date);
const dateB = new Date(b.activity.diaryDate.date);
return dateB - dateA;
})
.slice(0, parseInt(limit)) // Limit after filtering
.map(ma => ({