- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
/**
|
|
* Fix existing task titles that contain [object Object]
|
|
* caused by String(jsonbObject) in title template generation.
|
|
*
|
|
* Run inside Docker container:
|
|
* docker exec -i crm-app-1 npx tsx scripts/fix-contract-titles.ts
|
|
*/
|
|
|
|
import { db } from "../server/db";
|
|
import { storage } from "../server/storage";
|
|
import { buildSystemFieldValues, formatFieldValueForTitle } from "../server/routes/shared";
|
|
import { tasks } from "../shared/schema";
|
|
import { sql } from "drizzle-orm";
|
|
|
|
async function main() {
|
|
const badTasks = await db
|
|
.select()
|
|
.from(tasks)
|
|
.where(sql`${tasks.title} LIKE '%[object Object]%'`);
|
|
|
|
console.log(`Found ${badTasks.length} tasks with "[object Object]" in title`);
|
|
|
|
let updated = 0;
|
|
let skipped = 0;
|
|
|
|
for (const task of badTasks) {
|
|
try {
|
|
const form = await storage.getForm(task.formId, task.organizationId);
|
|
if (!form || !form.titleTemplate) {
|
|
skipped++;
|
|
console.log(` Skip #${task.id}: no form or no titleTemplate`);
|
|
continue;
|
|
}
|
|
|
|
const [allFormFields, savedValues, titleStatuses, titleUsers] = await Promise.all([
|
|
storage.getFormFields(task.formId, task.organizationId),
|
|
storage.getTaskFieldValues(task.id, task.organizationId),
|
|
storage.getFormStatuses(task.formId, task.organizationId),
|
|
storage.getUsersByOrganization(task.organizationId),
|
|
]);
|
|
|
|
const fieldCodeMap = new Map(allFormFields.map((f) => [f.code, f]));
|
|
const valueByFieldId = new Map(savedValues.map((v) => [v.fieldId, v.value]));
|
|
const usersMap = new Map(titleUsers.map((u) => [u.id, u]));
|
|
|
|
const sysValues = await buildSystemFieldValues(task, task.organizationId, { statuses: titleStatuses, usersMap });
|
|
const computedTitle = form.titleTemplate.replace(/\{\{([^}]+)\}\}/g, (_, code: string) => {
|
|
if (sysValues.has(code)) return sysValues.get(code)!;
|
|
const fld = fieldCodeMap.get(code);
|
|
if (!fld) return "";
|
|
const val = valueByFieldId.get(fld.id);
|
|
if (val === null || val === undefined) return "";
|
|
if (val === "__auto_prolongation__" && (fld.type === "date" || fld.type === "datetime")) {
|
|
return fld.autoProlongationLabel || "Автопролонгация";
|
|
}
|
|
return formatFieldValueForTitle(val, fld.type);
|
|
});
|
|
|
|
if (computedTitle !== task.title) {
|
|
await storage.updateTask(task.id, task.organizationId, { title: computedTitle });
|
|
updated++;
|
|
console.log(` Update #${task.id}: "${task.title}" -> "${computedTitle}"`);
|
|
} else {
|
|
skipped++;
|
|
console.log(` Skip #${task.id}: title unchanged after recalc`);
|
|
}
|
|
} catch (err: any) {
|
|
skipped++;
|
|
console.error(` Error #${task.id}:`, err.message);
|
|
}
|
|
}
|
|
|
|
console.log(`\nDone. Updated: ${updated}, Skipped: ${skipped}`);
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("Fatal error:", err);
|
|
process.exit(1);
|
|
});
|