feat(documents): папки шаблонов, маппинги и поле document_template; fix(batch): атомарное создание задач, idempotency и массовое удаление в реестре
This commit is contained in:
@@ -9,6 +9,7 @@ import { PackageService } from "./package.service";
|
||||
import { SignatureService } from "./signature.service";
|
||||
import { DocxImporterService } from "./docx-importer.service";
|
||||
import * as storage from "../storage/documents.storage";
|
||||
import { storage as appStorage } from "../storage/index";
|
||||
|
||||
export async function registerDocumentRoutes(app: Express) {
|
||||
const templateService = new DocumentTemplateService();
|
||||
@@ -49,6 +50,79 @@ export async function registerDocumentRoutes(app: Express) {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// ── Template Folders ───────────────────────────────────────────────────────
|
||||
|
||||
app.get("/api/documents/folders", authenticateToken, async (req: AuthenticatedRequest, res) => {
|
||||
const folders = await templateService.listFolders(req.organizationId!);
|
||||
res.json({ success: true, folders });
|
||||
});
|
||||
|
||||
app.post("/api/documents/folders", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
const folder = await templateService.createFolder({
|
||||
...req.body,
|
||||
organizationId: req.organizationId!,
|
||||
});
|
||||
res.status(201).json({ success: true, folder });
|
||||
});
|
||||
|
||||
app.put("/api/documents/folders/:id", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
const folder = await templateService.updateFolder(
|
||||
Number(req.params.id),
|
||||
req.organizationId!,
|
||||
req.body,
|
||||
);
|
||||
if (!folder) return res.status(404).json({ error: "Папка не найдена" });
|
||||
res.json({ success: true, folder });
|
||||
});
|
||||
|
||||
app.delete("/api/documents/folders/:id", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
await templateService.deleteFolder(Number(req.params.id), req.organizationId!);
|
||||
res.json({ success: true });
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : "Ошибка удаления папки";
|
||||
res.status(400).json({ error: message });
|
||||
}
|
||||
});
|
||||
|
||||
app.patch("/api/documents/folders/reorder", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
await templateService.reorderFolders(req.organizationId!, req.body.updates || []);
|
||||
res.json({ success: true });
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : "Ошибка изменения порядка папок";
|
||||
res.status(400).json({ error: message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/documents/folders/:id/mappings", authenticateToken, async (req: AuthenticatedRequest, res) => {
|
||||
const mappings = await templateService.listFolderMappings(Number(req.params.id), req.organizationId!);
|
||||
res.json({ success: true, mappings });
|
||||
});
|
||||
|
||||
app.post("/api/documents/folders/:id/mappings", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
const mapping = await templateService.createFolderMapping({
|
||||
...req.body,
|
||||
folderId: Number(req.params.id),
|
||||
});
|
||||
res.status(201).json({ success: true, mapping });
|
||||
});
|
||||
|
||||
app.put("/api/documents/folders/:id/mappings/:mappingId", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
const mapping = await templateService.updateFolderMapping(
|
||||
Number(req.params.mappingId),
|
||||
Number(req.params.id),
|
||||
req.body,
|
||||
);
|
||||
if (!mapping) return res.status(404).json({ error: "Маппинг не найден" });
|
||||
res.json({ success: true, mapping });
|
||||
});
|
||||
|
||||
app.delete("/api/documents/folders/:id/mappings/:mappingId", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
await templateService.deleteFolderMapping(Number(req.params.mappingId), Number(req.params.id));
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// ── Templates ──────────────────────────────────────────────────────────────
|
||||
|
||||
app.get("/api/documents/templates", authenticateToken, async (req: AuthenticatedRequest, res) => {
|
||||
@@ -56,6 +130,7 @@ export async function registerDocumentRoutes(app: Express) {
|
||||
organizationId: req.organizationId!,
|
||||
status: req.query.status as string | undefined,
|
||||
categoryId: req.query.categoryId ? Number(req.query.categoryId) : undefined,
|
||||
folderId: req.query.folderId ? Number(req.query.folderId) : undefined,
|
||||
formId: req.query.formId ? Number(req.query.formId) : undefined,
|
||||
});
|
||||
res.json({ success: true, templates });
|
||||
@@ -132,6 +207,16 @@ export async function registerDocumentRoutes(app: Express) {
|
||||
res.json({ success: true, template });
|
||||
});
|
||||
|
||||
app.patch("/api/documents/templates/:id/folder", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
const template = await templateService.moveTemplateToFolder(
|
||||
Number(req.params.id),
|
||||
req.organizationId!,
|
||||
req.body.folderId === null ? null : Number(req.body.folderId),
|
||||
);
|
||||
if (!template) return res.status(404).json({ error: "Шаблон не найден" });
|
||||
res.json({ success: true, template });
|
||||
});
|
||||
|
||||
app.delete("/api/documents/templates/:id", authenticateToken, requireAdmin, async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
await templateService.delete(Number(req.params.id), req.organizationId!);
|
||||
@@ -236,6 +321,40 @@ export async function registerDocumentRoutes(app: Express) {
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/documents/generate-link", authenticateToken, async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
const templateId = Number(req.query.templateId);
|
||||
const taskId = Number(req.query.taskId);
|
||||
const format = (req.query.format as string) || 'both';
|
||||
const returnTo = (req.query.returnTo as string) || '';
|
||||
|
||||
if (!templateId || !taskId) {
|
||||
return res.status(400).json({ error: "templateId и taskId обязательны" });
|
||||
}
|
||||
if (!['pdf', 'docx', 'both'].includes(format)) {
|
||||
return res.status(400).json({ error: "Недопустимый формат" });
|
||||
}
|
||||
|
||||
await generationService.generate({
|
||||
templateId,
|
||||
taskId,
|
||||
organizationId: req.organizationId!,
|
||||
userId: req.user!.id,
|
||||
outputFormat: format as any,
|
||||
});
|
||||
|
||||
let redirectUrl = returnTo;
|
||||
if (!redirectUrl) {
|
||||
const task = await appStorage.getTask(taskId, req.organizationId!);
|
||||
redirectUrl = task ? `/forms/${task.formId}/tasks/${taskId}` : '/';
|
||||
}
|
||||
res.redirect(redirectUrl);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : "Ошибка генерации документа";
|
||||
res.status(400).json({ error: message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/documents/generations/preview", authenticateToken, async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
const preview = await generationService.preview(
|
||||
|
||||
Reference in New Issue
Block a user