panels-origin/scripts/migrate-tenant-codes.ts
2026-08-21 16:55:01 -05:00

129 lines
4.3 KiB
TypeScript

/**
* One-off: tenant/company code = nombre(4) + YYMM; username = code lowercase.
* Usage: deno run -A scripts/migrate-tenant-codes.ts
*/
import { Database } from "jsr:@db/sqlite@0.12";
const PLATFORM = new URL("../data/platform.db", import.meta.url).pathname;
const APP = new URL("../data/app.db", import.meta.url).pathname;
function shortFromName(name: string): string {
const slug = (name ?? "")
.toString()
.normalize("NFD")
.replace(/\p{M}/gu, "")
.toUpperCase()
.replace(/[^A-Z0-9]+/g, "")
.slice(0, 4);
return slug.length >= 2 ? slug : (slug || "EMP").padEnd(2, "X").slice(0, 4);
}
function stampFromCreated(createdAt: string): string {
const m = createdAt.match(/^(\d{4})-(\d{2})/);
if (!m) {
const d = new Date();
return `${String(d.getFullYear()).slice(-2)}${String(d.getMonth() + 1).padStart(2, "0")}`;
}
return `${m[1].slice(-2)}${m[2]}`;
}
const platform = new Database(PLATFORM);
const app = new Database(APP);
platform.exec("PRAGMA foreign_keys = ON;");
app.exec("PRAGMA foreign_keys = ON;");
const tenants = platform.prepare(
"SELECT id, code, name, created_at FROM tenants ORDER BY id",
).all() as { id: number; code: string; name: string; created_at: string }[];
const used = new Set<string>([
...(app.prepare("SELECT code FROM companies").all() as { code: string }[]).map((r) => r.code),
...tenants.map((t) => t.code),
]);
function allocate(name: string, createdAt: string, oldCode: string): string {
const base = shortFromName(name);
const stamp = stampFromCreated(createdAt);
let candidate = `${base}${stamp}`;
if (candidate === oldCode || !used.has(candidate)) return candidate;
for (let n = 2; n < 1000; n++) {
const suffix = String(n);
const head = base.slice(0, Math.max(2, 4 - suffix.length));
candidate = `${head}${stamp}${suffix}`;
if (candidate === oldCode || !used.has(candidate)) return candidate;
}
throw new Error(`No unique code for ${name}`);
}
type MapRow = {
tenantId: number;
oldCode: string;
newCode: string;
oldUser: string;
newUser: string;
};
const mappings: MapRow[] = [];
for (const t of tenants) {
const newCode = allocate(t.name, t.created_at, t.code);
used.add(newCode);
mappings.push({
tenantId: t.id,
oldCode: t.code,
newCode,
oldUser: `adm.${t.code.toLowerCase()}`,
newUser: newCode.toLowerCase(),
});
}
console.log("Migrating:", mappings);
for (const m of mappings) {
if (m.oldCode !== m.newCode) {
platform.prepare("UPDATE tenants SET code = ? WHERE id = ?").run(m.newCode, m.tenantId);
const principal = app.prepare(
"SELECT id, code FROM companies WHERE tenant_id = ? AND kind = 'principal' ORDER BY id LIMIT 1",
).get(m.tenantId) as { id: number; code: string } | undefined;
if (principal && principal.code === m.oldCode) {
app.prepare("UPDATE companies SET code = ? WHERE id = ?").run(m.newCode, principal.id);
app.prepare("UPDATE workers SET hire_type = ? WHERE company_id = ? AND hire_type = ?")
.run(m.newCode, principal.id, m.oldCode);
}
}
const clash = app.prepare("SELECT id FROM users WHERE username = ?").get(m.newUser);
if (!clash) {
app.prepare(
"UPDATE users SET username = ? WHERE tenant_id = ? AND username = ?",
).run(m.newUser, m.tenantId, m.oldUser);
// also catch if username already equals old code lowercase without adm.
app.prepare(
"UPDATE users SET username = ? WHERE tenant_id = ? AND username = ? AND username != ?",
).run(m.newUser, m.tenantId, m.oldCode.toLowerCase(), m.newUser);
} else {
// ensure old adm.* row is renamed only if target not taken by another user
const old = app.prepare(
"SELECT id FROM users WHERE tenant_id = ? AND username = ?",
).get(m.tenantId, m.oldUser) as { id: number } | undefined;
if (old && (clash as { id: number }).id === old.id) {
// same row already correct
} else if (old) {
console.warn(`Skip user rename ${m.oldUser} → ${m.newUser}: target exists`);
}
}
}
console.log("TENANTS", platform.prepare("SELECT id, code, name FROM tenants").all());
console.log(
"PRINCIPALS",
app.prepare("SELECT id, code, tenant_id, kind FROM companies WHERE kind = 'principal'").all(),
);
console.log(
"USERS",
app.prepare("SELECT id, username, tenant_id, role FROM users").all(),
);
platform.close();
app.close();