feat(matching-runs): attach origin_run_id to new suggestions and expose pairs-per-run API
- Extend saveMatchingResults(eventId, suggestions, runId) and set originRunId - Scheduler/Admin run-now: always pass runId - Admin API: GET /api/admin/events/:slug/matching-runs/:runId/suggestions - Prisma: add compound index on (origin_run_id, status) - Frontend: add getRunSuggestions, expand row in MatchingRunsSection with 'Pairs created in this run' wording
This commit is contained in:
@@ -32,7 +32,7 @@ router.post('/events/:slug/run-now', authenticate, async (req, res, next) => {
|
||||
|
||||
try {
|
||||
const suggestions = await matchingService.runMatching(event.id);
|
||||
await matchingService.saveMatchingResults(event.id, suggestions);
|
||||
await matchingService.saveMatchingResults(event.id, suggestions, runRow.id);
|
||||
|
||||
const notFoundCount = suggestions.filter(s => s.status === SUGGESTION_STATUS.NOT_FOUND).length;
|
||||
const matchedCount = suggestions.filter(s => s.status === SUGGESTION_STATUS.PENDING).length;
|
||||
@@ -112,3 +112,85 @@ router.get('/events/:slug/matching-runs', authenticate, async (req, res, next) =
|
||||
|
||||
module.exports = router;
|
||||
|
||||
// GET /api/admin/events/:slug/matching-runs/:runId/suggestions - List suggestions created in this run
|
||||
router.get('/events/:slug/matching-runs/:runId/suggestions', authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const { slug, runId } = req.params;
|
||||
const onlyAssigned = String(req.query.onlyAssigned || 'true') === 'true';
|
||||
const includeNotFound = String(req.query.includeNotFound || 'false') === 'true';
|
||||
const limit = Math.min(parseInt(req.query.limit || '100', 10), 200);
|
||||
|
||||
const event = await prisma.event.findUnique({ where: { slug }, select: { id: true } });
|
||||
if (!event) {
|
||||
return res.status(404).json({ success: false, error: 'Event not found' });
|
||||
}
|
||||
|
||||
const run = await prisma.matchingRun.findFirst({
|
||||
where: { id: Number(runId), eventId: event.id },
|
||||
select: {
|
||||
id: true,
|
||||
trigger: true,
|
||||
status: true,
|
||||
startedAt: true,
|
||||
endedAt: true,
|
||||
matchedCount: true,
|
||||
notFoundCount: true,
|
||||
},
|
||||
});
|
||||
if (!run) {
|
||||
return res.status(404).json({ success: false, error: 'Run not found for event' });
|
||||
}
|
||||
|
||||
// Build where for suggestions in this run
|
||||
const where = {
|
||||
eventId: event.id,
|
||||
originRunId: Number(runId),
|
||||
};
|
||||
|
||||
if (onlyAssigned && !includeNotFound) {
|
||||
// Only pairs with assigned recorder
|
||||
where.recorderId = { not: null };
|
||||
}
|
||||
|
||||
const suggestions = await prisma.recordingSuggestion.findMany({
|
||||
where,
|
||||
take: limit,
|
||||
orderBy: { id: 'asc' },
|
||||
include: {
|
||||
heat: {
|
||||
include: {
|
||||
division: { select: { id: true, name: true, abbreviation: true } },
|
||||
competitionType: { select: { id: true, name: true, abbreviation: true } },
|
||||
user: { select: { id: true, username: true, avatar: true, city: true, country: true } },
|
||||
},
|
||||
},
|
||||
recorder: { select: { id: true, username: true, avatar: true, city: true, country: true } },
|
||||
},
|
||||
});
|
||||
|
||||
const formatted = suggestions
|
||||
.filter((s) => includeNotFound || s.status !== 'not_found')
|
||||
.map((s) => {
|
||||
const div = s.heat.division;
|
||||
const ct = s.heat.competitionType;
|
||||
const label = `${div?.name || ''} ${ct?.abbreviation || ct?.name || ''} #${s.heat.heatNumber}`.trim();
|
||||
return {
|
||||
id: s.id,
|
||||
status: s.status,
|
||||
heat: {
|
||||
id: s.heat.id,
|
||||
heatNumber: s.heat.heatNumber,
|
||||
label,
|
||||
division: div,
|
||||
competitionType: ct,
|
||||
},
|
||||
dancer: s.heat.user,
|
||||
recorder: s.recorder || null,
|
||||
};
|
||||
});
|
||||
|
||||
return res.json({ success: true, run, suggestions: formatted });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user