fix(number-fields): избегаем потери точности длинных чисел

- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
This commit is contained in:
2026-07-07 21:03:40 +03:00
commit 1f5ecb6da4
1089 changed files with 237546 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
import { eq, and } from "drizzle-orm";
import { db } from "../db";
import { formFolders, directoryFolders, forms, dataTables } from "@shared/schema";
// ===================== FORM FOLDERS =====================
export async function getFormFolders(organizationId: number) {
return await db
.select()
.from(formFolders)
.where(eq(formFolders.organizationId, organizationId))
.orderBy(formFolders.position, formFolders.id);
}
export async function createFormFolder(data: {
organizationId: number;
name: string;
parentId?: number | null;
position?: number;
}) {
const [folder] = await db
.insert(formFolders)
.values({
organizationId: data.organizationId,
name: data.name,
parentId: data.parentId ?? null,
position: data.position ?? 0,
})
.returning();
return folder;
}
export async function updateFormFolder(
id: number,
organizationId: number,
updates: { name?: string; parentId?: number | null; position?: number }
) {
const [folder] = await db
.update(formFolders)
.set({ ...updates, updatedAt: new Date() })
.where(and(eq(formFolders.id, id), eq(formFolders.organizationId, organizationId)))
.returning();
return folder;
}
export async function deleteFormFolder(id: number, organizationId: number) {
// Move child folders and forms to root
await db
.update(formFolders)
.set({ parentId: null, updatedAt: new Date() })
.where(and(eq(formFolders.parentId, id), eq(formFolders.organizationId, organizationId)));
await db
.update(forms)
.set({ folderId: null, updatedAt: new Date() })
.where(and(eq(forms.folderId, id), eq(forms.organizationId, organizationId)));
await db
.delete(formFolders)
.where(and(eq(formFolders.id, id), eq(formFolders.organizationId, organizationId)));
}
export async function moveFormToFolder(formId: number, organizationId: number, folderId: number | null) {
const [form] = await db
.update(forms)
.set({ folderId, updatedAt: new Date() })
.where(and(eq(forms.id, formId), eq(forms.organizationId, organizationId)))
.returning();
return form;
}
export async function moveFormFolder(
id: number,
organizationId: number,
parentId: number | null,
position: number
) {
const allFolders = await getFormFolders(organizationId);
const target = allFolders.find((f) => f.id === id);
if (!target) return null;
const siblings = allFolders
.filter((f) => f.id !== id && (f.parentId ?? null) === (parentId ?? null))
.sort((a, b) => a.position - b.position);
const reordered = [
...siblings.slice(0, position),
target,
...siblings.slice(position),
];
for (let i = 0; i < reordered.length; i++) {
await updateFormFolder(reordered[i].id, organizationId, {
parentId,
position: i,
});
}
return target;
}
export async function reorderFormFolders(
organizationId: number,
updates: Array<{ id: number; parentId: number | null; position: number }>
) {
if (updates.length === 0) return;
for (const update of updates) {
await updateFormFolder(update.id, organizationId, {
parentId: update.parentId,
position: update.position,
});
}
}
// ===================== DIRECTORY FOLDERS =====================
export async function getDirectoryFolders(organizationId: number) {
return await db
.select()
.from(directoryFolders)
.where(eq(directoryFolders.organizationId, organizationId))
.orderBy(directoryFolders.position, directoryFolders.id);
}
export async function createDirectoryFolder(data: {
organizationId: number;
name: string;
parentId?: number | null;
position?: number;
}) {
const [folder] = await db
.insert(directoryFolders)
.values({
organizationId: data.organizationId,
name: data.name,
parentId: data.parentId ?? null,
position: data.position ?? 0,
})
.returning();
return folder;
}
export async function updateDirectoryFolder(
id: number,
organizationId: number,
updates: { name?: string; parentId?: number | null; position?: number }
) {
const [folder] = await db
.update(directoryFolders)
.set({ ...updates, updatedAt: new Date() })
.where(and(eq(directoryFolders.id, id), eq(directoryFolders.organizationId, organizationId)))
.returning();
return folder;
}
export async function deleteDirectoryFolder(id: number, organizationId: number) {
await db
.update(directoryFolders)
.set({ parentId: null, updatedAt: new Date() })
.where(and(eq(directoryFolders.parentId, id), eq(directoryFolders.organizationId, organizationId)));
await db
.update(dataTables)
.set({ folderId: null, updatedAt: new Date() })
.where(and(eq(dataTables.folderId, id), eq(dataTables.organizationId, organizationId)));
await db
.delete(directoryFolders)
.where(and(eq(directoryFolders.id, id), eq(directoryFolders.organizationId, organizationId)));
}
export async function moveDirectoryToFolder(tableId: number, organizationId: number, folderId: number | null) {
const [table] = await db
.update(dataTables)
.set({ folderId, updatedAt: new Date() })
.where(and(eq(dataTables.id, tableId), eq(dataTables.organizationId, organizationId)))
.returning();
return table;
}
export async function moveDirectoryFolder(
id: number,
organizationId: number,
parentId: number | null,
position: number
) {
const allFolders = await getDirectoryFolders(organizationId);
const target = allFolders.find((f) => f.id === id);
if (!target) return null;
const siblings = allFolders
.filter((f) => f.id !== id && (f.parentId ?? null) === (parentId ?? null))
.sort((a, b) => a.position - b.position);
const reordered = [
...siblings.slice(0, position),
target,
...siblings.slice(position),
];
for (let i = 0; i < reordered.length; i++) {
await updateDirectoryFolder(reordered[i].id, organizationId, {
parentId,
position: i,
});
}
return target;
}
export async function reorderDirectoryFolders(
organizationId: number,
updates: Array<{ id: number; parentId: number | null; position: number }>
) {
if (updates.length === 0) return;
for (const update of updates) {
await updateDirectoryFolder(update.id, organizationId, {
parentId: update.parentId,
position: update.position,
});
}
}