Switch calendar events to isArchived model

This commit is contained in:
Ruslan Bakiev
2026-02-19 16:58:49 +07:00
parent 1047d5cb3f
commit f6fd11b3c4
9 changed files with 306 additions and 118 deletions

View File

@@ -120,7 +120,7 @@ type PendingChange =
start: string;
end: string | null;
note: string | null;
status: string | null;
isArchived: boolean;
}
| {
id: string;
@@ -195,7 +195,7 @@ async function buildCrmSnapshot(input: SnapshotOptions) {
take: 4,
},
events: {
select: { id: true, title: true, startsAt: true, endsAt: true, status: true },
select: { id: true, title: true, startsAt: true, endsAt: true, isArchived: true },
orderBy: { startsAt: "asc" },
where: { startsAt: { gte: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000) } },
take: 4,
@@ -276,7 +276,7 @@ async function buildCrmSnapshot(input: SnapshotOptions) {
title: e.title,
startsAt: iso(e.startsAt),
endsAt: iso(e.endsAt ?? e.startsAt),
status: e.status,
isArchived: e.isArchived,
note: e.note,
contact: e.contact?.name ?? null,
})),
@@ -327,7 +327,7 @@ async function buildCrmSnapshot(input: SnapshotOptions) {
title: e.title,
startsAt: iso(e.startsAt),
endsAt: iso(e.endsAt ?? e.startsAt),
status: e.status,
isArchived: e.isArchived,
})),
deals: c.deals.map((d) => ({
id: d.id,
@@ -507,7 +507,7 @@ export async function runLangGraphCrmAgentFor(input: {
start: z.string().optional(),
end: z.string().optional(),
note: z.string().optional(),
status: z.string().optional(),
archived: z.boolean().optional(),
channel: z.enum(["Telegram", "WhatsApp", "Instagram", "Phone", "Email"]).optional(),
kind: z.enum(["message", "call"]).optional(),
direction: z.enum(["in", "out"]).optional(),
@@ -547,7 +547,7 @@ export async function runLangGraphCrmAgentFor(input: {
startsAt: new Date(change.start),
endsAt: change.end ? new Date(change.end) : null,
note: change.note,
status: change.status,
isArchived: change.isArchived,
},
});
applied.push({ id: change.id, type: change.type, detail: `created event ${created.id}` });
@@ -775,7 +775,7 @@ export async function runLangGraphCrmAgentFor(input: {
start: iso(start),
end: end && !Number.isNaN(end.getTime()) ? iso(end) : null,
note: (raw.note ?? "").trim() || null,
status: (raw.status ?? "").trim() || null,
isArchived: Boolean(raw.archived),
});
if (raw.mode === "apply") {

View File

@@ -62,7 +62,7 @@ export async function exportDatasetFromPrismaFor(input: { teamId: string; userId
orderBy: { occurredAt: "asc" },
},
events: {
select: { title: true, startsAt: true, endsAt: true, status: true, note: true },
select: { title: true, startsAt: true, endsAt: true, isArchived: true, note: true },
orderBy: { startsAt: "asc" },
},
},
@@ -114,7 +114,7 @@ export async function exportDatasetFromPrismaFor(input: { teamId: string; userId
title: e.title,
startsAt: e.startsAt,
endsAt: e.endsAt,
status: e.status ?? null,
isArchived: e.isArchived,
note: e.note ?? null,
}),
);

View File

@@ -400,6 +400,10 @@ async function getDashboard(auth: AuthContext | null) {
end: (e.endsAt ?? e.startsAt).toISOString(),
contact: e.contact?.name ?? "",
note: e.note ?? "",
isArchived: Boolean(e.isArchived),
createdAt: e.createdAt.toISOString(),
archiveNote: e.archiveNote ?? "",
archivedAt: e.archivedAt?.toISOString() ?? "",
}));
const deals = dealsRaw.map((d) => ({
@@ -471,7 +475,8 @@ async function createCalendarEvent(auth: AuthContext | null, input: {
end?: string;
contact?: string;
note?: string;
status?: string;
archived?: boolean;
archiveNote?: string;
}) {
const ctx = requireAuth(auth);
@@ -488,15 +493,22 @@ async function createCalendarEvent(auth: AuthContext | null, input: {
: null;
const created = await prisma.calendarEvent.create({
data: {
teamId: ctx.teamId,
contactId: contact?.id ?? null,
title,
startsAt: start,
endsAt: end && !Number.isNaN(end.getTime()) ? end : null,
note: (input?.note ?? "").trim() || null,
status: (input?.status ?? "").trim() || null,
},
data: (() => {
const archived = Boolean(input?.archived);
const note = (input?.note ?? "").trim() || null;
const archiveNote = (input?.archiveNote ?? "").trim() || note;
return {
teamId: ctx.teamId,
contactId: contact?.id ?? null,
title,
startsAt: start,
endsAt: end && !Number.isNaN(end.getTime()) ? end : null,
note,
isArchived: archived,
archiveNote: archived ? archiveNote : null,
archivedAt: archived ? new Date() : null,
};
})(),
include: { contact: { select: { name: true } } },
});
@@ -507,6 +519,46 @@ async function createCalendarEvent(auth: AuthContext | null, input: {
end: (created.endsAt ?? created.startsAt).toISOString(),
contact: created.contact?.name ?? "",
note: created.note ?? "",
isArchived: Boolean(created.isArchived),
createdAt: created.createdAt.toISOString(),
archiveNote: created.archiveNote ?? "",
archivedAt: created.archivedAt?.toISOString() ?? "",
};
}
async function archiveCalendarEvent(auth: AuthContext | null, input: { id: string; archiveNote?: string }) {
const ctx = requireAuth(auth);
const id = String(input?.id ?? "").trim();
const archiveNote = String(input?.archiveNote ?? "").trim();
if (!id) throw new Error("id is required");
const existing = await prisma.calendarEvent.findFirst({
where: { id, teamId: ctx.teamId },
select: { id: true },
});
if (!existing) throw new Error("event not found");
const updated = await prisma.calendarEvent.update({
where: { id },
data: {
isArchived: true,
archiveNote: archiveNote || null,
archivedAt: new Date(),
},
include: { contact: { select: { name: true } } },
});
return {
id: updated.id,
title: updated.title,
start: updated.startsAt.toISOString(),
end: (updated.endsAt ?? updated.startsAt).toISOString(),
contact: updated.contact?.name ?? "",
note: updated.note ?? "",
isArchived: Boolean(updated.isArchived),
createdAt: updated.createdAt.toISOString(),
archiveNote: updated.archiveNote ?? "",
archivedAt: updated.archivedAt?.toISOString() ?? "",
};
}
@@ -790,6 +842,7 @@ export const crmGraphqlSchema = buildSchema(`
logPilotNote(text: String!): MutationResult!
toggleContactPin(contact: String!, text: String!): PinToggleResult!
createCalendarEvent(input: CreateCalendarEventInput!): CalendarEvent!
archiveCalendarEvent(input: ArchiveCalendarEventInput!): CalendarEvent!
createCommunication(input: CreateCommunicationInput!): MutationWithIdResult!
updateCommunicationTranscript(id: ID!, transcript: [String!]!): MutationWithIdResult!
updateFeedDecision(id: ID!, decision: String!, decisionNote: String): MutationWithIdResult!
@@ -815,7 +868,13 @@ export const crmGraphqlSchema = buildSchema(`
end: String
contact: String
note: String
status: String
archived: Boolean
archiveNote: String
}
input ArchiveCalendarEventInput {
id: ID!
archiveNote: String
}
input CreateCommunicationInput {
@@ -933,6 +992,10 @@ export const crmGraphqlSchema = buildSchema(`
end: String!
contact: String!
note: String!
isArchived: Boolean!
createdAt: String!
archiveNote: String!
archivedAt: String!
}
type Deal {
@@ -1030,9 +1093,12 @@ export const crmGraphqlRoot = {
toggleContactPin: async (args: { contact: string; text: string }, context: GraphQLContext) =>
toggleContactPin(context.auth, args.contact, args.text),
createCalendarEvent: async (args: { input: { title: string; start: string; end?: string; contact?: string; note?: string; status?: string } }, context: GraphQLContext) =>
createCalendarEvent: async (args: { input: { title: string; start: string; end?: string; contact?: string; note?: string; archived?: boolean; archiveNote?: string } }, context: GraphQLContext) =>
createCalendarEvent(context.auth, args.input),
archiveCalendarEvent: async (args: { input: { id: string; archiveNote?: string } }, context: GraphQLContext) =>
archiveCalendarEvent(context.auth, args.input),
createCommunication: async (
args: {
input: {

View File

@@ -9,7 +9,9 @@ type CalendarSnapshotRow = {
startsAt: string;
endsAt: string | null;
note: string | null;
status: string | null;
isArchived: boolean;
archiveNote: string | null;
archivedAt: string | null;
};
type ContactNoteSnapshotRow = {
@@ -101,7 +103,9 @@ export async function captureSnapshot(prisma: PrismaClient, teamId: string): Pro
startsAt: true,
endsAt: true,
note: true,
status: true,
isArchived: true,
archiveNote: true,
archivedAt: true,
},
take: 4000,
}),
@@ -135,7 +139,9 @@ export async function captureSnapshot(prisma: PrismaClient, teamId: string): Pro
startsAt: row.startsAt.toISOString(),
endsAt: row.endsAt?.toISOString() ?? null,
note: row.note ?? null,
status: row.status ?? null,
isArchived: Boolean(row.isArchived),
archiveNote: row.archiveNote ?? null,
archivedAt: row.archivedAt?.toISOString() ?? null,
},
]),
),
@@ -203,7 +209,9 @@ export function buildChangeSet(before: SnapshotState, after: SnapshotState): Cha
prev.startsAt !== row.startsAt ||
prev.endsAt !== row.endsAt ||
fmt(prev.note) !== fmt(row.note) ||
fmt(prev.status) !== fmt(row.status) ||
prev.isArchived !== row.isArchived ||
fmt(prev.archiveNote) !== fmt(row.archiveNote) ||
fmt(prev.archivedAt) !== fmt(row.archivedAt) ||
prev.contactId !== row.contactId
) {
items.push({
@@ -336,7 +344,9 @@ export async function rollbackChangeSet(prisma: PrismaClient, teamId: string, ch
startsAt: new Date(row.startsAt),
endsAt: row.endsAt ? new Date(row.endsAt) : null,
note: row.note,
status: row.status,
isArchived: row.isArchived,
archiveNote: row.archiveNote,
archivedAt: row.archivedAt ? new Date(row.archivedAt) : null,
},
create: {
id: row.id,
@@ -346,7 +356,9 @@ export async function rollbackChangeSet(prisma: PrismaClient, teamId: string, ch
startsAt: new Date(row.startsAt),
endsAt: row.endsAt ? new Date(row.endsAt) : null,
note: row.note,
status: row.status,
isArchived: row.isArchived,
archiveNote: row.archiveNote,
archivedAt: row.archivedAt ? new Date(row.archivedAt) : null,
},
});
continue;
@@ -412,4 +424,3 @@ export async function rollbackChangeSet(prisma: PrismaClient, teamId: string, ch
}
});
}