feat(documents): папки шаблонов, маппинги и поле document_template; fix(batch): атомарное создание задач, idempotency и массовое удаление в реестре

This commit is contained in:
2026-07-08 13:33:14 +03:00
parent fc8e85832c
commit 6826d82850
34 changed files with 3770 additions and 467 deletions

View File

@@ -1,5 +1,7 @@
import {
documentTemplateCategories,
documentTemplateFolders,
documentFolderMappings,
documentTemplates,
templateVariables,
organizationAssets,
@@ -9,6 +11,10 @@ import {
documentSignatures,
type DocumentTemplateCategory,
type InsertDocumentTemplateCategory,
type DocumentTemplateFolder,
type InsertDocumentTemplateFolder,
type DocumentFolderMapping,
type InsertDocumentFolderMapping,
type DocumentTemplate,
type InsertDocumentTemplate,
type TemplateVariable,
@@ -25,9 +31,9 @@ import {
type InsertDocumentSignature,
} from "@shared/schema";
import { db } from "../db";
import { eq, and, desc, asc, sql } from "drizzle-orm";
import { eq, and, desc, asc, sql, isNull } from "drizzle-orm";
// ── Document Template Categories ─────────────────────────────────────────────
// ── Document Template Categories (legacy) ────────────────────────────────────
export async function listDocumentTemplateCategories(organizationId: number): Promise<DocumentTemplateCategory[]> {
return db
@@ -69,12 +75,101 @@ export async function deleteDocumentTemplateCategory(id: number, organizationId:
.where(and(eq(documentTemplateCategories.id, id), eq(documentTemplateCategories.organizationId, organizationId)));
}
// ── Document Template Folders ─────────────────────────────────────────────────
export async function listDocumentTemplateFolders(organizationId: number): Promise<DocumentTemplateFolder[]> {
return db
.select()
.from(documentTemplateFolders)
.where(eq(documentTemplateFolders.organizationId, organizationId))
.orderBy(asc(documentTemplateFolders.position), asc(documentTemplateFolders.name));
}
export async function getDocumentTemplateFolder(id: number, organizationId: number): Promise<DocumentTemplateFolder | undefined> {
const [folder] = await db
.select()
.from(documentTemplateFolders)
.where(and(eq(documentTemplateFolders.id, id), eq(documentTemplateFolders.organizationId, organizationId)));
return folder;
}
export async function createDocumentTemplateFolder(data: InsertDocumentTemplateFolder): Promise<DocumentTemplateFolder> {
const [folder] = await db.insert(documentTemplateFolders).values(data).returning();
return folder;
}
export async function updateDocumentTemplateFolder(
id: number,
organizationId: number,
updates: Partial<DocumentTemplateFolder>,
): Promise<DocumentTemplateFolder | undefined> {
const [folder] = await db
.update(documentTemplateFolders)
.set({ ...updates, updatedAt: new Date() })
.where(and(eq(documentTemplateFolders.id, id), eq(documentTemplateFolders.organizationId, organizationId)))
.returning();
return folder;
}
export async function deleteDocumentTemplateFolder(id: number, organizationId: number): Promise<void> {
await db
.delete(documentTemplateFolders)
.where(and(eq(documentTemplateFolders.id, id), eq(documentTemplateFolders.organizationId, organizationId)));
}
// ── Document Folder Mappings ──────────────────────────────────────────────────
export async function listDocumentFolderMappings(folderId: number): Promise<DocumentFolderMapping[]> {
return db
.select()
.from(documentFolderMappings)
.where(eq(documentFolderMappings.folderId, folderId))
.orderBy(asc(documentFolderMappings.sortOrder), asc(documentFolderMappings.id));
}
export async function getDocumentFolderMapping(id: number, folderId: number): Promise<DocumentFolderMapping | undefined> {
const [mapping] = await db
.select()
.from(documentFolderMappings)
.where(and(eq(documentFolderMappings.id, id), eq(documentFolderMappings.folderId, folderId)));
return mapping;
}
export async function createDocumentFolderMapping(data: InsertDocumentFolderMapping): Promise<DocumentFolderMapping> {
const [mapping] = await db.insert(documentFolderMappings).values(data).returning();
return mapping;
}
export async function updateDocumentFolderMapping(
id: number,
folderId: number,
updates: Partial<DocumentFolderMapping>,
): Promise<DocumentFolderMapping | undefined> {
const [mapping] = await db
.update(documentFolderMappings)
.set(updates)
.where(and(eq(documentFolderMappings.id, id), eq(documentFolderMappings.folderId, folderId)))
.returning();
return mapping;
}
export async function deleteDocumentFolderMapping(id: number, folderId: number): Promise<void> {
await db
.delete(documentFolderMappings)
.where(and(eq(documentFolderMappings.id, id), eq(documentFolderMappings.folderId, folderId)));
}
export async function deleteDocumentFolderMappingsByFolder(folderId: number): Promise<void> {
await db.delete(documentFolderMappings).where(eq(documentFolderMappings.folderId, folderId));
}
// ── Document Templates ───────────────────────────────────────────────────────
export interface TemplateListFilters {
organizationId: number;
status?: string;
categoryId?: number;
folderId?: number;
formId?: number;
}
@@ -82,6 +177,13 @@ export async function listDocumentTemplates(filters: TemplateListFilters): Promi
const conditions = [eq(documentTemplates.organizationId, filters.organizationId)];
if (filters.status) conditions.push(eq(documentTemplates.status, filters.status));
if (filters.categoryId !== undefined) conditions.push(eq(documentTemplates.categoryId, filters.categoryId));
if (filters.folderId !== undefined) {
if (filters.folderId === null) {
conditions.push(isNull(documentTemplates.folderId));
} else {
conditions.push(eq(documentTemplates.folderId, filters.folderId));
}
}
if (filters.formId !== undefined) conditions.push(eq(documentTemplates.formId, filters.formId));
return db