feat(fields): мягкое удаление полей формы — архив удалённых с восстановлением и блок «Удалённые поля» в задаче
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { forms, formTabs, formFields, formStatuses, statusTransitions, tasks, bookmarks, type Form, type FormTab, type FormField, type FormStatus, type StatusTransition, type InsertForm, type InsertFormTab, type InsertFormField, type InsertFormStatus, type InsertStatusTransition } from "@shared/schema";
|
||||
import { db } from "../db";
|
||||
import { eq, and, desc, exists, sql } from "drizzle-orm";
|
||||
import { eq, and, desc, exists, sql, isNull } from "drizzle-orm";
|
||||
import crypto from "crypto";
|
||||
import { UserProfileStorage } from "./user-profile.storage";
|
||||
import { invalidateAccessibleTasksForOrg } from "../utils/cache";
|
||||
@@ -197,7 +197,7 @@ export class FormsStorage extends UserProfileStorage {
|
||||
.select()
|
||||
.from(formFields)
|
||||
.innerJoin(forms, eq(formFields.formId, forms.id))
|
||||
.where(and(eq(formFields.formId, formId), eq(forms.organizationId, organizationId)))
|
||||
.where(and(eq(formFields.formId, formId), eq(forms.organizationId, organizationId), isNull(formFields.deletedAt)))
|
||||
.orderBy(formFields.displayOrder)
|
||||
.then(results => results.map(r => r.form_fields));
|
||||
}
|
||||
@@ -207,11 +207,53 @@ export class FormsStorage extends UserProfileStorage {
|
||||
.select()
|
||||
.from(formFields)
|
||||
.innerJoin(forms, eq(formFields.formId, forms.id))
|
||||
.where(and(eq(formFields.formId, formId), eq(forms.organizationId, organizationId)))
|
||||
.where(and(eq(formFields.formId, formId), eq(forms.organizationId, organizationId), isNull(formFields.deletedAt)))
|
||||
.orderBy(formFields.id)
|
||||
.then(results => results.map(r => r.form_fields));
|
||||
}
|
||||
|
||||
// Архив удалённых (мягко) полей формы — для восстановления и блока «Удалённые поля» в задаче
|
||||
async getDeletedFormFields(formId: number, organizationId: number): Promise<FormField[]> {
|
||||
return await db
|
||||
.select()
|
||||
.from(formFields)
|
||||
.innerJoin(forms, eq(formFields.formId, forms.id))
|
||||
.where(and(eq(formFields.formId, formId), eq(forms.organizationId, organizationId), sql`${formFields.deletedAt} IS NOT NULL`))
|
||||
.orderBy(desc(formFields.deletedAt))
|
||||
.then(results => results.map(r => r.form_fields));
|
||||
}
|
||||
|
||||
// Восстановление удалённого поля. Код проверяем на конфликт с активным полем формы.
|
||||
async restoreFormField(id: number, formId: number, organizationId: number): Promise<FormField> {
|
||||
const [field] = await db
|
||||
.select()
|
||||
.from(formFields)
|
||||
.innerJoin(forms, eq(formFields.formId, forms.id))
|
||||
.where(and(eq(formFields.id, id), eq(formFields.formId, formId), eq(forms.organizationId, organizationId)));
|
||||
if (!field?.form_fields) throw new Error(`Поле ${id} не найдено`);
|
||||
if (!field.form_fields.deletedAt) throw new Error(`Поле "${field.form_fields.name}" не удалено`);
|
||||
|
||||
const [conflict] = await db
|
||||
.select({ id: formFields.id, name: formFields.name })
|
||||
.from(formFields)
|
||||
.where(and(
|
||||
eq(formFields.formId, formId),
|
||||
eq(formFields.code, field.form_fields.code),
|
||||
isNull(formFields.deletedAt),
|
||||
sql`${formFields.id} <> ${id}`,
|
||||
));
|
||||
if (conflict) {
|
||||
throw new Error(`Активное поле с кодом "${field.form_fields.code}" уже существует («${conflict.name}»). Переименуйте его или удалите.`);
|
||||
}
|
||||
|
||||
const [restored] = await db
|
||||
.update(formFields)
|
||||
.set({ deletedAt: null })
|
||||
.where(eq(formFields.id, id))
|
||||
.returning();
|
||||
return restored;
|
||||
}
|
||||
|
||||
async getFormFieldById(id: number, organizationId: number): Promise<FormField | undefined> {
|
||||
const [field] = await db
|
||||
.select({ field: formFields })
|
||||
@@ -243,11 +285,14 @@ export class FormsStorage extends UserProfileStorage {
|
||||
}
|
||||
|
||||
async deleteFormField(id: number, formId: number, organizationId: number): Promise<void> {
|
||||
// Мягкое удаление: значения в задачах сохраняются, поле доступно в архиве
|
||||
await db
|
||||
.delete(formFields)
|
||||
.update(formFields)
|
||||
.set({ deletedAt: new Date() })
|
||||
.where(and(
|
||||
eq(formFields.id, id),
|
||||
eq(formFields.formId, formId),
|
||||
isNull(formFields.deletedAt),
|
||||
// Убедимся что form принадлежит организации через subquery
|
||||
exists(db.select().from(forms).where(and(eq(forms.id, formId), eq(forms.organizationId, organizationId))))
|
||||
));
|
||||
@@ -290,9 +335,11 @@ export class FormsStorage extends UserProfileStorage {
|
||||
): Promise<FormField[]> {
|
||||
return await db.transaction(async (tx) => {
|
||||
for (const fieldId of fieldIdsToDelete) {
|
||||
// Мягкое удаление: значения в задачах сохраняются, поле уходит в архив
|
||||
await tx
|
||||
.delete(formFields)
|
||||
.where(and(eq(formFields.id, fieldId), eq(formFields.formId, formId)));
|
||||
.update(formFields)
|
||||
.set({ deletedAt: new Date() })
|
||||
.where(and(eq(formFields.id, fieldId), eq(formFields.formId, formId), isNull(formFields.deletedAt)));
|
||||
}
|
||||
|
||||
const resultFields: FormField[] = [];
|
||||
|
||||
Reference in New Issue
Block a user