feat(company): define prazo de reabertura por empresa
Some checks failed
Some checks failed
- Adiciona campo reopenWindowDays no cadastro de empresa (padrao 7 dias) - Ticket usa automaticamente o prazo da empresa ao ser resolvido - Remove selecao de prazo do modal de encerramento de ticket - Valor e gravado no ticket no momento da resolucao 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d12dcf9512
commit
034f6f47ff
5 changed files with 47 additions and 32 deletions
|
|
@ -82,6 +82,7 @@ export default defineSchema({
|
||||||
contacts: v.optional(v.any()),
|
contacts: v.optional(v.any()),
|
||||||
locations: v.optional(v.any()),
|
locations: v.optional(v.any()),
|
||||||
sla: v.optional(v.any()),
|
sla: v.optional(v.any()),
|
||||||
|
reopenWindowDays: v.optional(v.number()),
|
||||||
tags: v.optional(v.array(v.string())),
|
tags: v.optional(v.array(v.string())),
|
||||||
customFields: v.optional(v.any()),
|
customFields: v.optional(v.any()),
|
||||||
notes: v.optional(v.string()),
|
notes: v.optional(v.string()),
|
||||||
|
|
|
||||||
|
|
@ -3175,7 +3175,18 @@ export async function resolveTicketHandler(
|
||||||
throw new ConvexError("Chamado vinculado não encontrado")
|
throw new ConvexError("Chamado vinculado não encontrado")
|
||||||
}
|
}
|
||||||
|
|
||||||
const reopenDays = resolveReopenWindowDays(reopenWindowDays)
|
// Buscar prazo de reabertura da empresa do ticket (se existir)
|
||||||
|
let companyReopenDays: number | null = null
|
||||||
|
if (ticketDoc.companyId) {
|
||||||
|
const company = await ctx.db.get(ticketDoc.companyId)
|
||||||
|
if (company && typeof company.reopenWindowDays === "number") {
|
||||||
|
companyReopenDays = company.reopenWindowDays
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioridade: 1) valor passado explicitamente, 2) valor da empresa, 3) padrão
|
||||||
|
const effectiveReopenDays = reopenWindowDays ?? companyReopenDays
|
||||||
|
const reopenDays = resolveReopenWindowDays(effectiveReopenDays)
|
||||||
const reopenDeadline = computeReopenDeadline(now, reopenDays)
|
const reopenDeadline = computeReopenDeadline(now, reopenDays)
|
||||||
const normalizedStatus = "RESOLVED"
|
const normalizedStatus = "RESOLVED"
|
||||||
const relatedIdList = Array.from(
|
const relatedIdList = Array.from(
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,7 @@ function emptyCompany(tenantId: string): CompanyFormValues {
|
||||||
customFields: [],
|
customFields: [],
|
||||||
notes: null,
|
notes: null,
|
||||||
isAvulso: false,
|
isAvulso: false,
|
||||||
|
reopenWindowDays: 7,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1232,6 +1233,7 @@ export function CompanySheet({ tenantId, editor, onClose, onCreated, onUpdated }
|
||||||
/>
|
/>
|
||||||
<FieldError error={form.formState.errors.address?.message as string | undefined} />
|
<FieldError error={form.formState.errors.address?.message as string | undefined} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid gap-x-4 gap-y-3 md:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="contractedHoursPerMonth">Horas contratadas / mês</Label>
|
<Label htmlFor="contractedHoursPerMonth">Horas contratadas / mês</Label>
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -1245,6 +1247,24 @@ export function CompanySheet({ tenantId, editor, onClose, onCreated, onUpdated }
|
||||||
error={form.formState.errors.contractedHoursPerMonth?.message as string | undefined}
|
error={form.formState.errors.contractedHoursPerMonth?.message as string | undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="reopenWindowDays">Prazo de reabertura (dias)</Label>
|
||||||
|
<Input
|
||||||
|
id="reopenWindowDays"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={90}
|
||||||
|
placeholder="14"
|
||||||
|
{...form.register("reopenWindowDays", { valueAsNumber: true })}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Dias que o cliente pode reabrir tickets resolvidos.
|
||||||
|
</p>
|
||||||
|
<FieldError
|
||||||
|
error={form.formState.errors.reopenWindowDays?.message as string | undefined}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Checkbox id="isAvulso" checked={form.watch("isAvulso")} onCheckedChange={(checked) => form.setValue("isAvulso", Boolean(checked))} />
|
<Checkbox id="isAvulso" checked={form.watch("isAvulso")} onCheckedChange={(checked) => form.setValue("isAvulso", Boolean(checked))} />
|
||||||
<Label htmlFor="isAvulso" className="text-sm text-muted-foreground">
|
<Label htmlFor="isAvulso" className="text-sm text-muted-foreground">
|
||||||
|
|
|
||||||
|
|
@ -545,8 +545,7 @@ export function CloseTicketDialog({
|
||||||
Boolean(selectedTemplateId) ||
|
Boolean(selectedTemplateId) ||
|
||||||
shouldAdjustTime ||
|
shouldAdjustTime ||
|
||||||
adjustReason.trim().length > 0 ||
|
adjustReason.trim().length > 0 ||
|
||||||
linkedReference.trim().length > 0 ||
|
linkedReference.trim().length > 0
|
||||||
reopenWindowDays !== "14"
|
|
||||||
|
|
||||||
const canSaveDraft = hasFormChanges && !isSubmitting
|
const canSaveDraft = hasFormChanges && !isSubmitting
|
||||||
|
|
||||||
|
|
@ -651,12 +650,10 @@ export function CloseTicketDialog({
|
||||||
onWorkSummaryAdjusted?.(result)
|
onWorkSummaryAdjusted?.(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reopenDaysNumber = Number(reopenWindowDays)
|
|
||||||
await resolveTicketMutation({
|
await resolveTicketMutation({
|
||||||
ticketId: ticketId as unknown as Id<"tickets">,
|
ticketId: ticketId as unknown as Id<"tickets">,
|
||||||
actorId,
|
actorId,
|
||||||
resolvedWithTicketId: linkedTicketCandidate ? (linkedTicketCandidate.id as Id<"tickets">) : undefined,
|
resolvedWithTicketId: linkedTicketCandidate ? (linkedTicketCandidate.id as Id<"tickets">) : undefined,
|
||||||
reopenWindowDays: Number.isFinite(reopenDaysNumber) ? reopenDaysNumber : undefined,
|
|
||||||
})
|
})
|
||||||
await addComment({
|
await addComment({
|
||||||
ticketId: ticketId as unknown as Id<"tickets">,
|
ticketId: ticketId as unknown as Id<"tickets">,
|
||||||
|
|
@ -886,21 +883,6 @@ export function CloseTicketDialog({
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="reopen-window" className="text-sm font-medium text-neutral-800">
|
|
||||||
Reabertura permitida
|
|
||||||
</Label>
|
|
||||||
<Select value={reopenWindowDays} onValueChange={setReopenWindowDays} disabled={isSubmitting}>
|
|
||||||
<SelectTrigger id="reopen-window">
|
|
||||||
<SelectValue placeholder="Escolha o prazo" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="7">7 dias</SelectItem>
|
|
||||||
<SelectItem value="14">14 dias</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-xs text-neutral-500">Após esse período o ticket não poderá ser reaberto automaticamente.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,7 @@ export const companyFormSchema = z.object({
|
||||||
customFields: z.array(customFieldSchema).default([]),
|
customFields: z.array(customFieldSchema).default([]),
|
||||||
notes: z.string().trim().nullable().optional(),
|
notes: z.string().trim().nullable().optional(),
|
||||||
isAvulso: z.boolean().default(false),
|
isAvulso: z.boolean().default(false),
|
||||||
|
reopenWindowDays: z.number().int().min(1).max(90).nullable().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type CompanyFormValues = z.infer<typeof companyFormSchema>
|
export type CompanyFormValues = z.infer<typeof companyFormSchema>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue