mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 19:03:17 +00:00
129 lines
4.3 KiB
TypeScript
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();
|