Files
iistwin/server/routes/task-helpers.ts
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

100 lines
3.7 KiB
TypeScript

import type { Request } from 'express';
import { storage } from "../storage";
import {
buildFormText, generateFormSummary, upsertEmbedding, buildTaskText,
} from "../services/embedding.service";
async function indexFormAsync(formId: number, organizationId: number): Promise<void> {
try {
const [form, fields, statuses] = await Promise.all([
storage.getForm(formId, organizationId),
storage.getFormFields(formId, organizationId),
storage.getFormStatuses(formId, organizationId),
]);
if (!form) return;
const content = buildFormText(form, fields, statuses);
await upsertEmbedding(organizationId, 'form', formId, content, {
name: form.name,
description: form.description,
});
} catch (err) {
console.error('[RAG] indexFormAsync error:', err);
}
}
async function indexFormWithSummaryAsync(formId: number, organizationId: number): Promise<void> {
try {
const [form, fields, statuses] = await Promise.all([
storage.getForm(formId, organizationId),
storage.getFormFields(formId, organizationId),
storage.getFormStatuses(formId, organizationId),
]);
if (!form) return;
const summary = await generateFormSummary(form, fields, statuses);
if (summary) {
await storage.updateForm(formId, organizationId, { aiSummary: summary, aiSummaryIsAuto: true });
}
const indexedForm = summary ? { ...form, aiSummary: summary } : form;
const content = buildFormText(indexedForm, fields, statuses);
await upsertEmbedding(organizationId, 'form', formId, content, {
name: form.name,
description: form.description,
});
} catch (err) {
console.error('[RAG] indexFormWithSummaryAsync error:', err);
}
}
async function indexTaskAsync(taskId: number, organizationId: number): Promise<void> {
try {
const task = await storage.getTask(taskId, organizationId);
if (!task) return;
const [form, statuses, users, fieldValues, formFields] = await Promise.all([
storage.getForm(task.formId, organizationId),
storage.getFormStatuses(task.formId, organizationId),
storage.getUsersByOrganization(organizationId),
storage.getTaskFieldValues(taskId, organizationId),
storage.getFormFields(task.formId, organizationId),
]);
if (!form) return;
const statusMap = new Map(statuses.map(s => [s.id, s.name]));
const usersMap = new Map(users.map(u => [u.id, u]));
const assigneeUser = task.assignedTo ? usersMap.get(task.assignedTo) : null;
const assigneeName = assigneeUser
? `${assigneeUser.firstName ?? ''} ${assigneeUser.middleName ?? ''} ${assigneeUser.lastName ?? ''}`.trim() || assigneeUser.email
: null;
const fieldMap = new Map(formFields.map(f => [f.id, f]));
const enrichedFieldValues = fieldValues.map(fv => ({
name: fieldMap.get(fv.fieldId)?.name ?? '',
value: fv.value,
type: fieldMap.get(fv.fieldId)?.type ?? 'text',
}));
const content = buildTaskText(
task,
form.name,
statusMap.get(task.currentStatusId) ?? '',
assigneeName,
enrichedFieldValues
);
await upsertEmbedding(organizationId, 'task', taskId, content, {
formId: task.formId,
formName: form.name,
title: task.title,
});
} catch (err) {
console.error('[RAG] indexTaskAsync error:', err);
}
}
function parseOfflineTimestamp(req: Request): Date | undefined {
const h = req.headers['x-offline-enqueued-at'];
if (!h || typeof h !== 'string') return undefined;
const n = parseInt(h, 10);
if (isNaN(n)) return undefined;
const now = Date.now();
if (n > now || n < now - 30 * 24 * 60 * 60 * 1000) return undefined;
return new Date(n);
}
export { indexFormAsync, indexFormWithSummaryAsync, indexTaskAsync, parseOfflineTimestamp };