-
Empresa
-
- {ticket.company?.name ?? "Sem empresa"}
-
+
+
+
+
+
-
+ Empresa
+
+ -
+ {ticket.company?.name ?? "Sem empresa"}
+
+
+
+
-
+ Responsável
+
+ -
+ {ticket.assignee?.name ?? "Sem responsável"}
+
+
+
+
-
+ Solicitante
+
+ -
+ {ticket.requester?.name ?? ticket.requester?.email ?? "—"}
+
+
+
+
-
+ Criado em
+
+ -
+ {(() => {
+ const createdTimestamp = getTimestamp(ticket.createdAt)
+ return createdTimestamp
+ ? new Date(createdTimestamp).toLocaleDateString("pt-BR")
+ : "—"
+ })()}
+
+
+
+
+
+
+ Categoria:{" "}
+ {ticket.category?.name ?? "Sem categoria"}
+
-
-
Responsável
-
- {ticket.assignee?.name ?? "Sem responsável"}
-
-
-
-
Solicitante
-
- {ticket.requester?.name ?? ticket.requester?.email ?? "—"}
-
-
-
+
)
})}
diff --git a/src/server/company-service.ts b/src/server/company-service.ts
index 4ddab88..622278a 100644
--- a/src/server/company-service.ts
+++ b/src/server/company-service.ts
@@ -72,6 +72,99 @@ const STATE_REGISTRATION_TYPE_FROM_PRISMA: Record<
SIMPLES: "simples",
}
+const COMPANY_LEGACY_SELECT = {
+ id: true,
+ tenantId: true,
+ name: true,
+ slug: true,
+ isAvulso: true,
+ createdAt: true,
+ updatedAt: true,
+} satisfies Prisma.CompanySelect
+
+type LegacyCompanyRow = Prisma.CompanyGetPayload<{ select: typeof COMPANY_LEGACY_SELECT }>
+
+function isMissingProvisioningCodeColumn(error: unknown): boolean {
+ if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2022") {
+ const meta = error.meta as Record
| undefined
+ const metaColumn =
+ typeof meta?.column === "string"
+ ? meta.column
+ : typeof meta?.column_name === "string"
+ ? meta.column_name
+ : typeof meta?.model === "string"
+ ? meta.model
+ : null
+ if (metaColumn && metaColumn.toLowerCase().includes("provisioningcode")) return true
+ if (error.message.toLowerCase().includes("provisioningcode")) return true
+ }
+ return false
+}
+
+export async function safeCompanyFindMany(args: Prisma.CompanyFindManyArgs): Promise {
+ try {
+ return await prisma.company.findMany(args)
+ } catch (error) {
+ if (!isMissingProvisioningCodeColumn(error)) {
+ throw error
+ }
+
+ if (args.select || args.include) {
+ throw error
+ }
+
+ const legacyRows = (await prisma.company.findMany({
+ ...args,
+ select: COMPANY_LEGACY_SELECT,
+ })) as LegacyCompanyRow[]
+
+ return legacyRows.map(
+ (row) =>
+ ({
+ id: row.id,
+ tenantId: row.tenantId,
+ name: row.name,
+ slug: row.slug,
+ provisioningCode: "",
+ isAvulso: Boolean(row.isAvulso),
+ contractedHoursPerMonth: null,
+ cnpj: null,
+ domain: null,
+ phone: null,
+ description: null,
+ address: null,
+ legalName: null,
+ tradeName: null,
+ stateRegistration: null,
+ stateRegistrationType: null,
+ primaryCnae: null,
+ timezone: null,
+ businessHours: Prisma.JsonNull,
+ supportEmail: null,
+ billingEmail: null,
+ contactPreferences: Prisma.JsonNull,
+ clientDomains: Prisma.JsonNull,
+ communicationChannels: Prisma.JsonNull,
+ fiscalAddress: Prisma.JsonNull,
+ hasBranches: false,
+ regulatedEnvironments: Prisma.JsonNull,
+ privacyPolicyAccepted: false,
+ privacyPolicyReference: null,
+ privacyPolicyMetadata: Prisma.JsonNull,
+ contacts: Prisma.JsonNull,
+ locations: Prisma.JsonNull,
+ contracts: Prisma.JsonNull,
+ sla: Prisma.JsonNull,
+ tags: Prisma.JsonNull,
+ customFields: Prisma.JsonNull,
+ notes: null,
+ createdAt: row.createdAt,
+ updatedAt: row.updatedAt,
+ }) as Company
+ )
+ }
+}
+
export function formatZodError(error: ZodError) {
return error.issues.map((issue) => ({
path: issue.path.join("."),
@@ -328,171 +421,25 @@ export function normalizeCompany(company: Company): NormalizedCompany {
return {
...payload,
id: company.id,
- provisioningCode: company.provisioningCode ?? null,
+ provisioningCode: company.provisioningCode && company.provisioningCode.trim().length > 0
+ ? company.provisioningCode
+ : null,
createdAt: company.createdAt.toISOString(),
updatedAt: company.updatedAt.toISOString(),
}
}
-type RawCompanyRow = {
- id: string
- tenantId: string
- name: string
- slug: string
- provisioningCode: string | null
- isAvulso: number | boolean | null
- contractedHoursPerMonth: number | null
- cnpj: string | null
- domain: string | null
- phone: string | null
- description: string | null
- address: string | null
- legalName: string | null
- tradeName: string | null
- stateRegistration: string | null
- stateRegistrationType: string | null
- primaryCnae: string | null
- timezone: string | null
- businessHours: string | null
- supportEmail: string | null
- billingEmail: string | null
- contactPreferences: string | null
- clientDomains: string | null
- communicationChannels: string | null
- fiscalAddress: string | null
- hasBranches: number | null
- regulatedEnvironments: string | null
- privacyPolicyAccepted: number | null
- privacyPolicyReference: string | null
- privacyPolicyMetadata: string | null
- contacts: string | null
- locations: string | null
- contracts: string | null
- sla: string | null
- tags: string | null
- customFields: string | null
- notes: string | null
- createdAt: string
- updatedAt: string
-}
-
-function parseJsonValue(value: string | null): Prisma.JsonValue | null {
- if (value === null || value === undefined) return null
- const trimmed = value.trim()
- if (!trimmed || trimmed.toLowerCase() === "null") return null
- try {
- return JSON.parse(trimmed) as Prisma.JsonValue
- } catch (error) {
- console.warn("[company-service] Invalid JSON detected; coercing to null.", { value, error })
- return null
- }
-}
-
-function mapRawRowToCompany(row: RawCompanyRow): Company {
- return {
- id: row.id,
- tenantId: row.tenantId,
- name: row.name,
- slug: row.slug,
- provisioningCode: row.provisioningCode ?? "",
- isAvulso: Boolean(row.isAvulso),
- contractedHoursPerMonth: row.contractedHoursPerMonth,
- cnpj: row.cnpj,
- domain: row.domain,
- phone: row.phone,
- description: row.description,
- address: row.address,
- legalName: row.legalName,
- tradeName: row.tradeName,
- stateRegistration: row.stateRegistration,
- stateRegistrationType: row.stateRegistrationType
- ? (row.stateRegistrationType as DbCompanyStateRegistrationType)
- : null,
- primaryCnae: row.primaryCnae,
- timezone: row.timezone,
- businessHours: parseJsonValue(row.businessHours),
- supportEmail: row.supportEmail,
- billingEmail: row.billingEmail,
- contactPreferences: parseJsonValue(row.contactPreferences),
- clientDomains: parseJsonValue(row.clientDomains),
- communicationChannels: parseJsonValue(row.communicationChannels),
- fiscalAddress: parseJsonValue(row.fiscalAddress),
- hasBranches: Boolean(row.hasBranches),
- regulatedEnvironments: parseJsonValue(row.regulatedEnvironments),
- privacyPolicyAccepted: Boolean(row.privacyPolicyAccepted),
- privacyPolicyReference: row.privacyPolicyReference,
- privacyPolicyMetadata: parseJsonValue(row.privacyPolicyMetadata),
- contacts: parseJsonValue(row.contacts),
- locations: parseJsonValue(row.locations),
- contracts: parseJsonValue(row.contracts),
- sla: parseJsonValue(row.sla),
- tags: parseJsonValue(row.tags),
- customFields: parseJsonValue(row.customFields),
- notes: row.notes,
- createdAt: new Date(row.createdAt),
- updatedAt: new Date(row.updatedAt),
- }
-}
-
-const COMPANY_BASE_SELECT = Prisma.sql`
- SELECT
- id,
- tenantId,
- name,
- slug,
- provisioningCode,
- isAvulso,
- contractedHoursPerMonth,
- cnpj,
- domain,
- phone,
- description,
- address,
- legalName,
- tradeName,
- stateRegistration,
- stateRegistrationType,
- primaryCnae,
- timezone,
- CAST(businessHours AS TEXT) AS businessHours,
- supportEmail,
- billingEmail,
- CAST(contactPreferences AS TEXT) AS contactPreferences,
- CAST(clientDomains AS TEXT) AS clientDomains,
- CAST(communicationChannels AS TEXT) AS communicationChannels,
- CAST(fiscalAddress AS TEXT) AS fiscalAddress,
- hasBranches,
- CAST(regulatedEnvironments AS TEXT) AS regulatedEnvironments,
- privacyPolicyAccepted,
- privacyPolicyReference,
- CAST(privacyPolicyMetadata AS TEXT) AS privacyPolicyMetadata,
- CAST(contacts AS TEXT) AS contacts,
- CAST(locations AS TEXT) AS locations,
- CAST(contracts AS TEXT) AS contracts,
- CAST(sla AS TEXT) AS sla,
- CAST(tags AS TEXT) AS tags,
- CAST(customFields AS TEXT) AS customFields,
- notes,
- createdAt,
- updatedAt
- FROM "Company"
-`
-
export async function fetchCompaniesByTenant(tenantId: string): Promise {
- const rows = await prisma.$queryRaw(Prisma.sql`
- ${COMPANY_BASE_SELECT}
- WHERE tenantId = ${tenantId}
- ORDER BY name ASC
- `)
- return rows.map(mapRawRowToCompany)
+ return safeCompanyFindMany({
+ where: { tenantId },
+ orderBy: { name: "asc" },
+ })
}
export async function fetchCompanyById(id: string): Promise {
- const rows = await prisma.$queryRaw(Prisma.sql`
- ${COMPANY_BASE_SELECT}
- WHERE id = ${id}
- LIMIT 1
- `)
- const row = rows[0]
- return row ? mapRawRowToCompany(row) : null
+ const rows = await safeCompanyFindMany({
+ where: { id },
+ take: 1,
+ })
+ return rows[0] ?? null
}