feat(groups): assign tutor to group
This commit is contained in:
@@ -773,6 +773,7 @@ async function ensureUserTables() {
|
||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token)`);
|
||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at)`);
|
||||
await pool.query('ALTER TABLE audit_log ADD COLUMN IF NOT EXISTS user_id INT REFERENCES users(id) ON DELETE SET NULL');
|
||||
await pool.query('ALTER TABLE groups ADD COLUMN IF NOT EXISTS tutor_id INT REFERENCES users(id) ON DELETE SET NULL');
|
||||
}
|
||||
|
||||
async function ensureFirstAdmin() {
|
||||
@@ -883,12 +884,20 @@ app.get('/api/users', requireAuth, requireAdmin, async (_, res) => {
|
||||
COALESCE(array_agg(ub.branch_id) FILTER (WHERE ub.branch_id IS NOT NULL), '{}') AS branch_ids
|
||||
FROM users u
|
||||
LEFT JOIN user_branches ub ON ub.user_id = u.id
|
||||
WHERE u.role = 'tutor'
|
||||
GROUP BY u.id
|
||||
ORDER BY u.id`
|
||||
);
|
||||
res.json(rows.map(r => ({ ...r, branch_ids: r.branch_ids || [] })));
|
||||
});
|
||||
|
||||
app.get('/api/users/tutors', requireAuth, async (_, res) => {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT id, name, username FROM users WHERE role = 'tutor' AND is_active = true ORDER BY COALESCE(NULLIF(name, ''), username)`
|
||||
);
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
app.get('/api/users/branches', requireAuth, async (req, res) => {
|
||||
const s = branchScope(req.user);
|
||||
const params = [];
|
||||
@@ -1627,8 +1636,8 @@ app.post('/api/restore', requireAdmin, uploadBackup.single('backup'), async (req
|
||||
}
|
||||
for (const x of ndata.groups) {
|
||||
await client.query(
|
||||
'INSERT INTO groups (id, name, created_at, day_of_week, time_start, time_end, branch_id) VALUES ($1,$2,$3,$4,$5,$6,$7)',
|
||||
[x.id, x.name, x.created_at, x.day_of_week, x.time_start, x.time_end, x.branch_id]
|
||||
'INSERT INTO groups (id, name, created_at, day_of_week, time_start, time_end, branch_id, tutor_id) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)',
|
||||
[x.id, x.name, x.created_at, x.day_of_week, x.time_start, x.time_end, x.branch_id, x.tutor_id]
|
||||
);
|
||||
}
|
||||
for (const x of ndata.students) {
|
||||
@@ -2091,7 +2100,7 @@ app.get('/api/groups/active', apiLimiter, async (_, res) => {
|
||||
});
|
||||
|
||||
app.put('/api/groups/:id', requireAuth, async (req, res) => {
|
||||
const { name, day_of_week, time_start, time_end, branch_id } = req.body;
|
||||
const { name, day_of_week, time_start, time_end, branch_id, tutor_id } = req.body;
|
||||
if (!(await groupBelongsToBranches(req.user, req.params.id))) {
|
||||
return res.status(403).json({ error: 'Нет доступа к этой группе' });
|
||||
}
|
||||
@@ -2099,6 +2108,13 @@ app.put('/api/groups/:id', requireAuth, async (req, res) => {
|
||||
if (!isAdmin && branch_id !== undefined) {
|
||||
return res.status(403).json({ error: 'Назначение филиала — только для администратора' });
|
||||
}
|
||||
const tutorProvided = tutor_id !== undefined;
|
||||
let effectiveTutor = null;
|
||||
if (tutor_id !== undefined && tutor_id !== null && tutor_id !== '') {
|
||||
const t = await pool.query(`SELECT id FROM users WHERE id = $1 AND role = 'tutor'`, [tutor_id]);
|
||||
if (!t.rows.length) return res.status(400).json({ error: 'Пользователь не найден или не является тутором' });
|
||||
effectiveTutor = t.rows[0].id;
|
||||
}
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`UPDATE groups SET
|
||||
@@ -2106,12 +2122,15 @@ app.put('/api/groups/:id', requireAuth, async (req, res) => {
|
||||
day_of_week = $2,
|
||||
time_start = $3,
|
||||
time_end = $4,
|
||||
branch_id = $5
|
||||
WHERE id = $6 RETURNING *`,
|
||||
branch_id = $5,
|
||||
tutor_id = CASE WHEN $6::boolean THEN $7::int ELSE tutor_id END
|
||||
WHERE id = $8 RETURNING *`,
|
||||
[name,
|
||||
day_of_week === undefined || day_of_week === null || day_of_week === '' ? null : day_of_week,
|
||||
time_start || null, time_end || null,
|
||||
branch_id === undefined || branch_id === null || branch_id === '' ? null : branch_id,
|
||||
tutorProvided,
|
||||
tutorProvided ? effectiveTutor : null,
|
||||
req.params.id]
|
||||
);
|
||||
await logAudit(req, 'group.update', { id: req.params.id, ...req.body });
|
||||
@@ -2125,17 +2144,23 @@ app.put('/api/groups/:id', requireAuth, async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/groups', requireAuth, async (req, res) => {
|
||||
const { name, branch_id } = req.body;
|
||||
const { name, branch_id, tutor_id } = req.body;
|
||||
if (!name?.trim()) return res.status(400).json({ error: 'Name required' });
|
||||
const isAdmin = req.user.role === 'admin';
|
||||
const effectiveBranch = branch_id === undefined || branch_id === null || branch_id === '' ? null : Number(branch_id);
|
||||
if (!isAdmin && branch_id !== undefined && branch_id !== null && branch_id !== '') {
|
||||
return res.status(403).json({ error: 'Назначение филиала — только для администратора' });
|
||||
}
|
||||
let effectiveTutor = null;
|
||||
if (tutor_id !== undefined && tutor_id !== null && tutor_id !== '') {
|
||||
const t = await pool.query(`SELECT id FROM users WHERE id = $1 AND role = 'tutor'`, [tutor_id]);
|
||||
if (!t.rows.length) return res.status(400).json({ error: 'Пользователь не найден или не является тутором' });
|
||||
effectiveTutor = t.rows[0].id;
|
||||
}
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
'INSERT INTO groups (name, branch_id) VALUES ($1, $2) RETURNING *',
|
||||
[name.trim(), isAdmin ? effectiveBranch : null]
|
||||
'INSERT INTO groups (name, branch_id, tutor_id) VALUES ($1, $2, $3) RETURNING *',
|
||||
[name.trim(), isAdmin ? effectiveBranch : null, effectiveTutor]
|
||||
);
|
||||
await logAudit(req, 'group.create', { id: rows[0].id, name: name.trim(), branch_id });
|
||||
invalidateGroups();
|
||||
|
||||
Reference in New Issue
Block a user