180 lines
6.3 KiB
TypeScript
180 lines
6.3 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
import { ZodError } from "zod"
|
|
import { Prisma } from "@prisma/client"
|
|
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library"
|
|
|
|
import { prisma } from "@/lib/prisma"
|
|
import { assertStaffSession } from "@/lib/auth-server"
|
|
import { isAdmin } from "@/lib/authz"
|
|
import { removeConvexCompany, syncConvexCompany } from "@/server/companies-sync"
|
|
import {
|
|
buildCompanyData,
|
|
fetchCompanyById,
|
|
formatZodError,
|
|
normalizeCompany,
|
|
sanitizeCompanyInput,
|
|
} from "@/server/company-service"
|
|
import type { CompanyFormValues } from "@/lib/schemas/company"
|
|
|
|
export const runtime = "nodejs"
|
|
|
|
function mergePayload(base: CompanyFormValues, updates: Record<string, unknown>): Record<string, unknown> {
|
|
const merged: Record<string, unknown> = { ...base, ...updates }
|
|
|
|
if (!("businessHours" in updates)) merged.businessHours = base.businessHours
|
|
if (!("communicationChannels" in updates)) merged.communicationChannels = base.communicationChannels
|
|
if (!("clientDomains" in updates)) merged.clientDomains = base.clientDomains
|
|
if (!("fiscalAddress" in updates)) merged.fiscalAddress = base.fiscalAddress
|
|
if (!("regulatedEnvironments" in updates)) merged.regulatedEnvironments = base.regulatedEnvironments
|
|
if (!("contacts" in updates)) merged.contacts = base.contacts
|
|
if (!("locations" in updates)) merged.locations = base.locations
|
|
if (!("contracts" in updates)) merged.contracts = base.contracts
|
|
if (!("sla" in updates)) merged.sla = base.sla
|
|
if (!("tags" in updates)) merged.tags = base.tags
|
|
if (!("customFields" in updates)) merged.customFields = base.customFields
|
|
|
|
if ("privacyPolicy" in updates) {
|
|
const incoming = updates.privacyPolicy
|
|
if (incoming && typeof incoming === "object") {
|
|
merged.privacyPolicy = {
|
|
...base.privacyPolicy,
|
|
...incoming,
|
|
}
|
|
}
|
|
} else {
|
|
merged.privacyPolicy = base.privacyPolicy
|
|
}
|
|
|
|
merged.tenantId = base.tenantId
|
|
return merged
|
|
}
|
|
|
|
export async function PATCH(
|
|
request: Request,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
const session = await assertStaffSession()
|
|
if (!session) return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
|
if (!isAdmin(session.user.role)) {
|
|
return NextResponse.json({ error: "Apenas administradores podem editar empresas" }, { status: 403 })
|
|
}
|
|
|
|
const { id } = await params
|
|
|
|
try {
|
|
const existing = await fetchCompanyById(id)
|
|
if (!existing) {
|
|
return NextResponse.json({ error: "Empresa não encontrada" }, { status: 404 })
|
|
}
|
|
|
|
if (existing.tenantId !== (session.user.tenantId ?? existing.tenantId)) {
|
|
return NextResponse.json({ error: "Acesso negado" }, { status: 403 })
|
|
}
|
|
|
|
const rawBody = (await request.json()) as Record<string, unknown>
|
|
const normalized = normalizeCompany(existing)
|
|
const {
|
|
id: _ignoreId,
|
|
provisioningCode: _ignoreCode,
|
|
createdAt: _createdAt,
|
|
updatedAt: _updatedAt,
|
|
...baseForm
|
|
} = normalized
|
|
void _ignoreId
|
|
void _ignoreCode
|
|
void _createdAt
|
|
void _updatedAt
|
|
|
|
const mergedInput = mergePayload(baseForm, rawBody)
|
|
const form = sanitizeCompanyInput(mergedInput, existing.tenantId)
|
|
const createData = buildCompanyData(form, existing.tenantId)
|
|
const { tenantId: _omitTenant, ...updateData } = createData
|
|
void _omitTenant
|
|
|
|
const company = await prisma.company.update({
|
|
where: { id },
|
|
data: updateData,
|
|
})
|
|
|
|
if (company.provisioningCode) {
|
|
const synced = await syncConvexCompany({
|
|
tenantId: company.tenantId,
|
|
slug: company.slug,
|
|
name: company.name,
|
|
provisioningCode: company.provisioningCode,
|
|
})
|
|
if (!synced) {
|
|
console.warn("[admin.companies] Convex não configurado; atualização aplicada apenas no Prisma.")
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ company: normalizeCompany(company) })
|
|
} catch (error) {
|
|
if (error instanceof ZodError) {
|
|
return NextResponse.json({ error: "Dados inválidos", issues: formatZodError(error) }, { status: 422 })
|
|
}
|
|
if (error instanceof PrismaClientKnownRequestError && error.code === "P2002") {
|
|
return NextResponse.json({ error: "Já existe uma empresa com este slug." }, { status: 409 })
|
|
}
|
|
console.error("Failed to update company", error)
|
|
return NextResponse.json({ error: "Falha ao atualizar empresa" }, { status: 500 })
|
|
}
|
|
}
|
|
|
|
export async function DELETE(
|
|
_: Request,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
const session = await assertStaffSession()
|
|
if (!session) return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
|
if (!isAdmin(session.user.role)) {
|
|
return NextResponse.json({ error: "Apenas administradores podem excluir empresas" }, { status: 403 })
|
|
}
|
|
const { id } = await params
|
|
|
|
const company = await fetchCompanyById(id)
|
|
|
|
if (!company) {
|
|
return NextResponse.json({ error: "Empresa não encontrada" }, { status: 404 })
|
|
}
|
|
|
|
if (company.tenantId !== (session.user.tenantId ?? company.tenantId)) {
|
|
return NextResponse.json({ error: "Acesso negado" }, { status: 403 })
|
|
}
|
|
|
|
try {
|
|
const result = await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
|
const users = await tx.user.updateMany({
|
|
where: { companyId: company.id, tenantId: company.tenantId },
|
|
data: { companyId: null },
|
|
})
|
|
const tickets = await tx.ticket.updateMany({
|
|
where: { companyId: company.id, tenantId: company.tenantId },
|
|
data: { companyId: null },
|
|
})
|
|
await tx.company.delete({ where: { id: company.id } })
|
|
return { detachedUsers: users.count, detachedTickets: tickets.count }
|
|
})
|
|
|
|
if (company.slug) {
|
|
const removed = await removeConvexCompany({
|
|
tenantId: company.tenantId,
|
|
slug: company.slug,
|
|
})
|
|
if (!removed) {
|
|
console.warn("[admin.companies] Convex não configurado; empresa removida apenas no Prisma.")
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ ok: true, ...result })
|
|
} catch (error) {
|
|
if (error instanceof PrismaClientKnownRequestError && error.code === "P2003") {
|
|
return NextResponse.json(
|
|
{ error: "Não é possível remover esta empresa pois existem registros vinculados." },
|
|
{ status: 409 }
|
|
)
|
|
}
|
|
console.error("Failed to delete company", error)
|
|
return NextResponse.json({ error: "Falha ao excluir empresa" }, { status: 500 })
|
|
}
|
|
}
|