feat: ajustar board de tickets

This commit is contained in:
Esdras Renan 2025-10-27 14:50:17 -03:00
parent e9a8bd6b9b
commit d23987eda8
7 changed files with 434 additions and 429 deletions

View file

@ -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<string, unknown> | 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<Company[]> {
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<Company[]> {
const rows = await prisma.$queryRaw<RawCompanyRow[]>(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<Company | null> {
const rows = await prisma.$queryRaw<RawCompanyRow[]>(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
}