- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
158 lines
5.7 KiB
TypeScript
158 lines
5.7 KiB
TypeScript
import { Router } from "express";
|
|
import { storage } from "../storage";
|
|
import { authenticateToken, requireFormAdminOrPermission } from "../middleware/auth.middleware";
|
|
import { z } from "zod";
|
|
|
|
const router = Router();
|
|
|
|
// GET /api/forms/:id/access-rules
|
|
router.get('/api/forms/:id/access-rules',
|
|
authenticateToken,
|
|
async (req, res) => {
|
|
try {
|
|
const formId = parseInt(req.params.id);
|
|
if (isNaN(formId)) return res.status(400).json({ error: 'Неверный ID формы' });
|
|
const rules = await storage.getFormAccessRules(formId, (req as any).organizationId!);
|
|
res.json({ success: true, rules });
|
|
} catch (err) {
|
|
console.error('Get form access rules error:', err);
|
|
res.status(500).json({ error: 'Ошибка при получении правил доступа' });
|
|
}
|
|
}
|
|
);
|
|
|
|
// POST /api/forms/:id/access-rules
|
|
router.post('/api/forms/:id/access-rules',
|
|
authenticateToken,
|
|
async (req, res) => {
|
|
try {
|
|
const formId = parseInt(req.params.id);
|
|
if (isNaN(formId)) return res.status(400).json({ error: 'Неверный ID формы' });
|
|
|
|
const schema = z.object({
|
|
targetType: z.enum(['user', 'role']),
|
|
targetId: z.number().positive(),
|
|
accessLevel: z.enum(['participate', 'view_all', 'admin']).default('participate'),
|
|
});
|
|
const body = schema.parse(req.body);
|
|
|
|
const rule = await storage.createFormAccessRule({
|
|
formId,
|
|
organizationId: (req as any).organizationId!,
|
|
targetType: body.targetType,
|
|
targetId: body.targetId,
|
|
accessLevel: body.accessLevel,
|
|
});
|
|
res.json({ success: true, rule });
|
|
} catch (err) {
|
|
if (err instanceof z.ZodError) {
|
|
return res.status(400).json({ error: 'Неверные данные', details: err.errors });
|
|
}
|
|
console.error('Create form access rule error:', err);
|
|
res.status(500).json({ error: 'Ошибка при создании правила доступа' });
|
|
}
|
|
}
|
|
);
|
|
|
|
// PUT /api/forms/:id/access-rules/:ruleId
|
|
router.put('/api/forms/:id/access-rules/:ruleId',
|
|
authenticateToken,
|
|
async (req, res) => {
|
|
try {
|
|
const ruleId = parseInt(req.params.ruleId);
|
|
if (isNaN(ruleId)) return res.status(400).json({ error: 'Неверный ID правила' });
|
|
|
|
const schema = z.object({
|
|
accessLevel: z.enum(['participate', 'view_all', 'admin']),
|
|
});
|
|
const body = schema.parse(req.body);
|
|
|
|
const rule = await storage.updateFormAccessRule(ruleId, (req as any).organizationId!, {
|
|
accessLevel: body.accessLevel,
|
|
});
|
|
res.json({ success: true, rule });
|
|
} catch (err) {
|
|
if (err instanceof z.ZodError) {
|
|
return res.status(400).json({ error: 'Неверные данные', details: err.errors });
|
|
}
|
|
console.error('Update form access rule error:', err);
|
|
res.status(500).json({ error: 'Ошибка при обновлении правила доступа' });
|
|
}
|
|
}
|
|
);
|
|
|
|
// DELETE /api/forms/:id/access-rules/:ruleId
|
|
router.delete('/api/forms/:id/access-rules/:ruleId',
|
|
authenticateToken,
|
|
requireFormAdminOrPermission('forms.manage'),
|
|
async (req, res) => {
|
|
try {
|
|
const ruleId = parseInt(req.params.ruleId);
|
|
if (isNaN(ruleId)) return res.status(400).json({ error: 'Неверный ID правила' });
|
|
await storage.deleteFormAccessRule(ruleId, (req as any).organizationId!);
|
|
res.json({ success: true });
|
|
} catch (err) {
|
|
console.error('Delete form access rule error:', err);
|
|
res.status(500).json({ error: 'Ошибка при удалении правила доступа' });
|
|
}
|
|
}
|
|
);
|
|
|
|
// PUT /api/forms/:id/visibility
|
|
router.put('/api/forms/:id/visibility',
|
|
authenticateToken,
|
|
async (req, res) => {
|
|
try {
|
|
const formId = parseInt(req.params.id);
|
|
if (isNaN(formId)) return res.status(400).json({ error: 'Неверный ID формы' });
|
|
|
|
const schema = z.object({
|
|
visibility: z.enum(['organization', 'restricted']),
|
|
});
|
|
const body = schema.parse(req.body);
|
|
|
|
const { db } = await import("../db");
|
|
const { forms } = await import("@shared/schema");
|
|
const { eq, and } = await import("drizzle-orm");
|
|
const [form] = await db.update(forms)
|
|
.set({ visibility: body.visibility })
|
|
.where(and(eq(forms.id, formId), eq(forms.organizationId, (req as any).organizationId!)))
|
|
.returning();
|
|
res.json({ success: true, form });
|
|
} catch (err) {
|
|
if (err instanceof z.ZodError) {
|
|
return res.status(400).json({ error: 'Неверные данные', details: err.errors });
|
|
}
|
|
console.error('Update form visibility error:', err);
|
|
res.status(500).json({ error: 'Ошибка при обновлении видимости формы' });
|
|
}
|
|
}
|
|
);
|
|
|
|
// POST /api/forms/:id/transfer-ownership
|
|
router.post('/api/forms/:id/transfer-ownership',
|
|
authenticateToken,
|
|
async (req, res) => {
|
|
try {
|
|
const formId = parseInt(req.params.id);
|
|
if (isNaN(formId)) return res.status(400).json({ error: 'Неверный ID формы' });
|
|
|
|
const schema = z.object({
|
|
newAuthorId: z.number().positive(),
|
|
});
|
|
const body = schema.parse(req.body);
|
|
|
|
await storage.transferFormOwnership(formId, (req as any).organizationId!, body.newAuthorId);
|
|
res.json({ success: true });
|
|
} catch (err) {
|
|
if (err instanceof z.ZodError) {
|
|
return res.status(400).json({ error: 'Неверные данные', details: err.errors });
|
|
}
|
|
console.error('Transfer form ownership error:', err);
|
|
res.status(500).json({ error: 'Ошибка при передаче прав на форму' });
|
|
}
|
|
}
|
|
);
|
|
|
|
export default router;
|