fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
This commit is contained in:
80
scripts/fix-contract-titles.ts
Normal file
80
scripts/fix-contract-titles.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
Reference in New Issue
Block a user