feat: add company management and manager role support
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
parent
409cbea7b9
commit
854887f499
16 changed files with 955 additions and 126 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { mutation } from "./_generated/server";
|
||||
import type { Id } from "./_generated/dataModel"
|
||||
import { mutation } from "./_generated/server"
|
||||
|
||||
export const seedDemo = mutation({
|
||||
args: {},
|
||||
|
|
@ -51,30 +52,139 @@ export const seedDemo = mutation({
|
|||
|
||||
const queueChamados = queuesBySlug.get("chamados");
|
||||
const queueLaboratorio = queuesBySlug.get("laboratorio");
|
||||
if (!queueChamados || !queueLaboratorio) {
|
||||
const queueVisitas = queuesBySlug.get("visitas");
|
||||
if (!queueChamados || !queueLaboratorio || !queueVisitas) {
|
||||
throw new Error("Filas padrão não foram inicializadas");
|
||||
}
|
||||
|
||||
// Ensure users
|
||||
async function ensureUser(name: string, email: string, role = "AGENT") {
|
||||
const found = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant_email", (q) => q.eq("tenantId", tenantId).eq("email", email))
|
||||
.first();
|
||||
if (found) {
|
||||
const updates: Record<string, unknown> = {};
|
||||
if (found.name !== name) updates.name = name;
|
||||
if ((found.role ?? "AGENT") !== role) updates.role = role;
|
||||
const desiredAvatar = role === "CUSTOMER" ? found.avatarUrl ?? undefined : `https://avatar.vercel.sh/${name.split(" ")[0]}`;
|
||||
if (found.avatarUrl !== desiredAvatar) updates.avatarUrl = desiredAvatar;
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await ctx.db.patch(found._id, updates);
|
||||
}
|
||||
return found._id;
|
||||
}
|
||||
return await ctx.db.insert("users", { tenantId, name, email, role, avatarUrl: role === "CUSTOMER" ? undefined : `https://avatar.vercel.sh/${name.split(" ")[0]}` });
|
||||
function slugify(value: string) {
|
||||
return value
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.replace(/[^\w\s-]/g, "")
|
||||
.trim()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.toLowerCase();
|
||||
}
|
||||
const adminId = await ensureUser("Administrador", "admin@sistema.dev", "ADMIN");
|
||||
|
||||
function defaultAvatar(name: string, email: string, role: string) {
|
||||
const normalizedRole = role.toUpperCase();
|
||||
if (normalizedRole === "CUSTOMER" || normalizedRole === "MANAGER") {
|
||||
return `https://i.pravatar.cc/150?u=${encodeURIComponent(email)}`;
|
||||
}
|
||||
const first = name.split(" ")[0] ?? email;
|
||||
return `https://avatar.vercel.sh/${encodeURIComponent(first)}`;
|
||||
}
|
||||
|
||||
async function ensureCompany(def: {
|
||||
name: string;
|
||||
slug?: string;
|
||||
cnpj?: string;
|
||||
domain?: string;
|
||||
phone?: string;
|
||||
description?: string;
|
||||
address?: string;
|
||||
}): Promise<Id<"companies">> {
|
||||
const slug = def.slug ?? slugify(def.name);
|
||||
const existing = await ctx.db
|
||||
.query("companies")
|
||||
.withIndex("by_tenant_slug", (q) => q.eq("tenantId", tenantId).eq("slug", slug))
|
||||
.first();
|
||||
const now = Date.now();
|
||||
const payload = {
|
||||
tenantId,
|
||||
name: def.name,
|
||||
slug,
|
||||
cnpj: def.cnpj ?? undefined,
|
||||
domain: def.domain ?? undefined,
|
||||
phone: def.phone ?? undefined,
|
||||
description: def.description ?? undefined,
|
||||
address: def.address ?? undefined,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
if (existing) {
|
||||
const updates: Record<string, unknown> = {};
|
||||
if (existing.name !== payload.name) updates.name = payload.name;
|
||||
if (existing.cnpj !== payload.cnpj) updates.cnpj = payload.cnpj;
|
||||
if (existing.domain !== payload.domain) updates.domain = payload.domain;
|
||||
if (existing.phone !== payload.phone) updates.phone = payload.phone;
|
||||
if (existing.description !== payload.description) updates.description = payload.description;
|
||||
if (existing.address !== payload.address) updates.address = payload.address;
|
||||
if (Object.keys(updates).length > 0) {
|
||||
updates.updatedAt = now;
|
||||
await ctx.db.patch(existing._id, updates);
|
||||
}
|
||||
return existing._id;
|
||||
}
|
||||
return await ctx.db.insert("companies", payload);
|
||||
}
|
||||
|
||||
async function ensureUser(params: {
|
||||
name: string;
|
||||
email: string;
|
||||
role?: string;
|
||||
companyId?: Id<"companies">;
|
||||
avatarUrl?: string;
|
||||
}): Promise<Id<"users">> {
|
||||
const normalizedEmail = params.email.trim().toLowerCase();
|
||||
const normalizedRole = (params.role ?? "CUSTOMER").toUpperCase();
|
||||
const desiredAvatar = params.avatarUrl ?? defaultAvatar(params.name, normalizedEmail, normalizedRole);
|
||||
const existing = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tenant_email", (q) => q.eq("tenantId", tenantId).eq("email", normalizedEmail))
|
||||
.first();
|
||||
if (existing) {
|
||||
const updates: Record<string, unknown> = {};
|
||||
if (existing.name !== params.name) updates.name = params.name;
|
||||
if ((existing.role ?? "CUSTOMER") !== normalizedRole) updates.role = normalizedRole;
|
||||
if ((existing.avatarUrl ?? undefined) !== desiredAvatar) updates.avatarUrl = desiredAvatar;
|
||||
if ((existing.companyId ?? undefined) !== (params.companyId ?? undefined)) updates.companyId = params.companyId ?? undefined;
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await ctx.db.patch(existing._id, updates);
|
||||
}
|
||||
return existing._id;
|
||||
}
|
||||
return await ctx.db.insert("users", {
|
||||
tenantId,
|
||||
name: params.name,
|
||||
email: normalizedEmail,
|
||||
role: normalizedRole,
|
||||
avatarUrl: desiredAvatar,
|
||||
companyId: params.companyId ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const companiesSeed = [
|
||||
{
|
||||
name: "Atlas Engenharia Digital",
|
||||
slug: "atlas-engenharia",
|
||||
cnpj: "12.345.678/0001-90",
|
||||
domain: "atlasengenharia.com.br",
|
||||
phone: "+55 11 4002-8922",
|
||||
description: "Transformação digital para empresas de engenharia e construção.",
|
||||
address: "Av. Paulista, 1234 - Bela Vista, São Paulo/SP",
|
||||
},
|
||||
{
|
||||
name: "Omni Saúde Integrada",
|
||||
slug: "omni-saude",
|
||||
cnpj: "45.678.912/0001-05",
|
||||
domain: "omnisaude.com.br",
|
||||
phone: "+55 31 3555-7788",
|
||||
description: "Rede de clínicas com serviços de telemedicina e prontuário eletrônico.",
|
||||
address: "Rua da Bahia, 845 - Centro, Belo Horizonte/MG",
|
||||
},
|
||||
];
|
||||
|
||||
const companyIds = new Map<string, Id<"companies">>();
|
||||
for (const company of companiesSeed) {
|
||||
const id = await ensureCompany(company);
|
||||
companyIds.set(company.slug, id);
|
||||
}
|
||||
|
||||
const adminId = await ensureUser({ name: "Administrador", email: "admin@sistema.dev", role: "ADMIN" });
|
||||
const staffRoster = [
|
||||
{ name: "Gabriel Oliveira", email: "gabriel.oliveira@rever.com.br" },
|
||||
{ name: "George Araujo", email: "george.araujo@rever.com.br" },
|
||||
|
|
@ -86,10 +196,62 @@ export const seedDemo = mutation({
|
|||
{ name: "Weslei Magalhães", email: "weslei@rever.com.br" },
|
||||
];
|
||||
|
||||
const staffIds = await Promise.all(staffRoster.map((staff) => ensureUser(staff.name, staff.email)));
|
||||
const staffIds = await Promise.all(
|
||||
staffRoster.map((staff) => ensureUser({ name: staff.name, email: staff.email, role: "AGENT" })),
|
||||
);
|
||||
const defaultAssigneeId = staffIds[0] ?? adminId;
|
||||
const eduardaId = await ensureUser("Eduarda Rocha", "eduarda.rocha@example.com", "CUSTOMER");
|
||||
const clienteDemoId = await ensureUser("Cliente Demo", "cliente.demo@sistema.dev", "CUSTOMER");
|
||||
|
||||
const atlasCompanyId = companyIds.get("atlas-engenharia");
|
||||
const omniCompanyId = companyIds.get("omni-saude");
|
||||
if (!atlasCompanyId || !omniCompanyId) {
|
||||
throw new Error("Empresas padrão não foram inicializadas");
|
||||
}
|
||||
|
||||
const atlasManagerId = await ensureUser({
|
||||
name: "Mariana Andrade",
|
||||
email: "mariana.andrade@atlasengenharia.com.br",
|
||||
role: "MANAGER",
|
||||
companyId: atlasCompanyId,
|
||||
});
|
||||
|
||||
const joaoAtlasId = await ensureUser({
|
||||
name: "João Pedro Ramos",
|
||||
email: "joao.ramos@atlasengenharia.com.br",
|
||||
role: "CUSTOMER",
|
||||
companyId: atlasCompanyId,
|
||||
});
|
||||
await ensureUser({
|
||||
name: "Aline Rezende",
|
||||
email: "aline.rezende@atlasengenharia.com.br",
|
||||
role: "CUSTOMER",
|
||||
companyId: atlasCompanyId,
|
||||
});
|
||||
|
||||
const omniManagerId = await ensureUser({
|
||||
name: "Fernanda Lima",
|
||||
email: "fernanda.lima@omnisaude.com.br",
|
||||
role: "MANAGER",
|
||||
companyId: omniCompanyId,
|
||||
});
|
||||
|
||||
const ricardoOmniId = await ensureUser({
|
||||
name: "Ricardo Matos",
|
||||
email: "ricardo.matos@omnisaude.com.br",
|
||||
role: "CUSTOMER",
|
||||
companyId: omniCompanyId,
|
||||
});
|
||||
await ensureUser({
|
||||
name: "Luciana Prado",
|
||||
email: "luciana.prado@omnisaude.com.br",
|
||||
role: "CUSTOMER",
|
||||
companyId: omniCompanyId,
|
||||
});
|
||||
const clienteDemoId = await ensureUser({
|
||||
name: "Cliente Demo",
|
||||
email: "cliente.demo@sistema.dev",
|
||||
role: "CUSTOMER",
|
||||
companyId: omniCompanyId,
|
||||
});
|
||||
|
||||
const templateDefinitions = [
|
||||
{
|
||||
|
|
@ -146,13 +308,25 @@ export const seedDemo = mutation({
|
|||
priority: "URGENT",
|
||||
channel: "EMAIL",
|
||||
queueId: queue1,
|
||||
requesterId: eduardaId,
|
||||
requesterId: joaoAtlasId,
|
||||
assigneeId: defaultAssigneeId,
|
||||
companyId: atlasCompanyId,
|
||||
createdAt: now - 1000 * 60 * 60 * 5,
|
||||
updatedAt: now - 1000 * 60 * 10,
|
||||
tags: ["portal", "cliente"],
|
||||
});
|
||||
await ctx.db.insert("ticketEvents", { ticketId: t1, type: "CREATED", createdAt: now - 1000 * 60 * 60 * 5, payload: {} });
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
ticketId: t1,
|
||||
type: "CREATED",
|
||||
createdAt: now - 1000 * 60 * 60 * 5,
|
||||
payload: {},
|
||||
});
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
ticketId: t1,
|
||||
type: "MANAGER_NOTIFIED",
|
||||
createdAt: now - 1000 * 60 * 60 * 4,
|
||||
payload: { managerUserId: atlasManagerId },
|
||||
});
|
||||
|
||||
const t2 = await ctx.db.insert("tickets", {
|
||||
tenantId,
|
||||
|
|
@ -163,13 +337,54 @@ export const seedDemo = mutation({
|
|||
priority: "HIGH",
|
||||
channel: "WHATSAPP",
|
||||
queueId: queue2,
|
||||
requesterId: clienteDemoId,
|
||||
requesterId: ricardoOmniId,
|
||||
assigneeId: defaultAssigneeId,
|
||||
companyId: omniCompanyId,
|
||||
createdAt: now - 1000 * 60 * 60 * 8,
|
||||
updatedAt: now - 1000 * 60 * 30,
|
||||
tags: ["Integração", "erp"],
|
||||
});
|
||||
await ctx.db.insert("ticketEvents", { ticketId: t2, type: "CREATED", createdAt: now - 1000 * 60 * 60 * 8, payload: {} });
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
ticketId: t2,
|
||||
type: "CREATED",
|
||||
createdAt: now - 1000 * 60 * 60 * 8,
|
||||
payload: {},
|
||||
});
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
ticketId: t2,
|
||||
type: "MANAGER_NOTIFIED",
|
||||
createdAt: now - 1000 * 60 * 60 * 7,
|
||||
payload: { managerUserId: omniManagerId },
|
||||
});
|
||||
|
||||
const t3 = await ctx.db.insert("tickets", {
|
||||
tenantId,
|
||||
reference: ++ref,
|
||||
subject: "Visita técnica para instalação de roteadores",
|
||||
summary: "Equipe Omni solicita agenda para instalação de novos pontos de rede",
|
||||
status: "OPEN",
|
||||
priority: "MEDIUM",
|
||||
channel: "PHONE",
|
||||
queueId: queueVisitas._id,
|
||||
requesterId: clienteDemoId,
|
||||
assigneeId: defaultAssigneeId,
|
||||
companyId: omniCompanyId,
|
||||
createdAt: now - 1000 * 60 * 60 * 3,
|
||||
updatedAt: now - 1000 * 60 * 20,
|
||||
tags: ["visita", "infraestrutura"],
|
||||
});
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
ticketId: t3,
|
||||
type: "CREATED",
|
||||
createdAt: now - 1000 * 60 * 60 * 3,
|
||||
payload: {},
|
||||
});
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
ticketId: t3,
|
||||
type: "VISIT_SCHEDULED",
|
||||
createdAt: now - 1000 * 60 * 15,
|
||||
payload: { scheduledFor: now + 1000 * 60 * 60 * 24 },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue