/** * 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); });