feat(visits): concluir/reabrir visita sem poluir agenda
This commit is contained in:
parent
8f2c00a75a
commit
66559eafbf
9 changed files with 264 additions and 31 deletions
|
|
@ -273,6 +273,8 @@ export default defineSchema({
|
|||
slaPausedBy: v.optional(v.string()),
|
||||
slaPausedMs: v.optional(v.number()),
|
||||
dueAt: v.optional(v.number()), // ms since epoch
|
||||
visitStatus: v.optional(v.string()),
|
||||
visitPerformedAt: v.optional(v.number()),
|
||||
firstResponseAt: v.optional(v.number()),
|
||||
resolvedAt: v.optional(v.number()),
|
||||
closedAt: v.optional(v.number()),
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ const MAX_COMMENT_CHARS = 20000;
|
|||
const DEFAULT_REOPEN_DAYS = 7;
|
||||
const MAX_REOPEN_DAYS = 14;
|
||||
const VISIT_QUEUE_KEYWORDS = ["visita", "visitas", "in loco", "laboratório", "laboratorio", "lab"];
|
||||
const VISIT_STATUSES = new Set(["scheduled", "en_route", "in_service", "done", "no_show", "canceled"]);
|
||||
const VISIT_COMPLETED_STATUSES = new Set(["done", "no_show", "canceled"]);
|
||||
|
||||
type AnyCtx = QueryCtx | MutationCtx;
|
||||
|
||||
|
|
@ -2003,6 +2005,8 @@ export const getById = query({
|
|||
resolvedWithTicketId: t.resolvedWithTicketId ? String(t.resolvedWithTicketId) : null,
|
||||
reopenDeadline: t.reopenDeadline ?? null,
|
||||
reopenedAt: t.reopenedAt ?? null,
|
||||
visitStatus: t.visitStatus ?? null,
|
||||
visitPerformedAt: t.visitPerformedAt ?? null,
|
||||
description: undefined,
|
||||
customFields: customFieldsRecord,
|
||||
timeline: timelineRecords.map((ev) => {
|
||||
|
|
@ -2247,6 +2251,8 @@ export const create = mutation({
|
|||
slaPolicyId: undefined,
|
||||
dueAt: visitDueAt && isVisitQueue ? visitDueAt : undefined,
|
||||
customFields: normalizedCustomFields.length ? normalizedCustomFields : undefined,
|
||||
visitStatus: isVisitQueue ? "scheduled" : undefined,
|
||||
visitPerformedAt: undefined,
|
||||
...slaFields,
|
||||
});
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
|
|
@ -3546,6 +3552,8 @@ export const updateVisitSchedule = mutation({
|
|||
const actor = viewer.user
|
||||
await ctx.db.patch(ticketId, {
|
||||
dueAt: visitDate,
|
||||
visitStatus: "scheduled",
|
||||
visitPerformedAt: undefined,
|
||||
updatedAt: now,
|
||||
})
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
|
|
@ -3564,6 +3572,69 @@ export const updateVisitSchedule = mutation({
|
|||
},
|
||||
})
|
||||
|
||||
export const updateVisitStatus = mutation({
|
||||
args: {
|
||||
ticketId: v.id("tickets"),
|
||||
actorId: v.id("users"),
|
||||
status: v.string(),
|
||||
performedAt: v.optional(v.number()),
|
||||
},
|
||||
handler: async (ctx, { ticketId, actorId, status, performedAt }) => {
|
||||
const ticket = await ctx.db.get(ticketId)
|
||||
if (!ticket) {
|
||||
throw new ConvexError("Ticket não encontrado")
|
||||
}
|
||||
const ticketDoc = ticket as Doc<"tickets">
|
||||
const viewer = await requireTicketStaff(ctx, actorId, ticketDoc)
|
||||
if (viewer.role === "MANAGER") {
|
||||
throw new ConvexError("Gestores não podem alterar o status da visita")
|
||||
}
|
||||
const normalizedStatus = (status ?? "").toLowerCase()
|
||||
if (!VISIT_STATUSES.has(normalizedStatus)) {
|
||||
throw new ConvexError("Status da visita inválido")
|
||||
}
|
||||
if (!ticketDoc.queueId) {
|
||||
throw new ConvexError("Este ticket não possui fila configurada")
|
||||
}
|
||||
const queue = (await ctx.db.get(ticketDoc.queueId)) as Doc<"queues"> | null
|
||||
if (!queue) {
|
||||
throw new ConvexError("Fila não encontrada para este ticket")
|
||||
}
|
||||
const queueLabel = (normalizeQueueName(queue) ?? queue.name ?? "").toLowerCase()
|
||||
const isVisitQueue = VISIT_QUEUE_KEYWORDS.some((keyword) => queueLabel.includes(keyword))
|
||||
if (!isVisitQueue) {
|
||||
throw new ConvexError("Somente tickets da fila de visitas possuem status de visita")
|
||||
}
|
||||
const now = Date.now()
|
||||
const completed = VISIT_COMPLETED_STATUSES.has(normalizedStatus)
|
||||
const resolvedPerformedAt =
|
||||
completed && typeof performedAt === "number" && Number.isFinite(performedAt) ? performedAt : completed ? now : undefined
|
||||
|
||||
await ctx.db.patch(ticketId, {
|
||||
visitStatus: normalizedStatus,
|
||||
visitPerformedAt: completed ? resolvedPerformedAt : undefined,
|
||||
// Mantemos dueAt para não perder o agendamento original.
|
||||
dueAt: ticketDoc.dueAt,
|
||||
updatedAt: now,
|
||||
})
|
||||
|
||||
await ctx.db.insert("ticketEvents", {
|
||||
ticketId,
|
||||
type: "VISIT_STATUS_CHANGED",
|
||||
payload: {
|
||||
status: normalizedStatus,
|
||||
performedAt: resolvedPerformedAt,
|
||||
actorId,
|
||||
actorName: viewer.user.name,
|
||||
actorAvatar: viewer.user.avatarUrl ?? undefined,
|
||||
},
|
||||
createdAt: now,
|
||||
})
|
||||
|
||||
return { status: normalizedStatus }
|
||||
},
|
||||
})
|
||||
|
||||
export const changeQueue = mutation({
|
||||
args: { ticketId: v.id("tickets"), queueId: v.id("queues"), actorId: v.id("users") },
|
||||
handler: async (ctx, { ticketId, queueId, actorId }) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue