Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ ADAMRMS_EMAIL=
ADAMRMS_PASSWORD=
ADAMRMS_BASE=https://dash.adam-rms.com
ADAMRMS_PROJECT_TYPE_ID=18
ADAMRMS_TENTATIVE_STATUS_ID=1051
ADAMRMS_CONFIRMED_STATUS_ID=1053
ADAMRMS_CANCELLED_STATUS_ID=1058
SESSION_SECRET=change me
18 changes: 17 additions & 1 deletion app/(authenticated)/calendar/[eventID]/EventActionsUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,23 @@ export function EventActionsUI(props: { event: EventObjectType }) {
},
onConfirm() {
startTransition(async () => {
await reinstateEvent(props.event.event_id);
const result = await reinstateEvent(props.event.event_id);
if (!result.ok) {
modals.openModal({
id: "reinstate-error",
title: "Error",
children: (
<>
<Text>{result.errors?.root ?? `Unknown error (${JSON.stringify(result)})`}</Text>
<Button
onClick={() => modals.closeModal("reinstate-error")}
>
Close
</Button>
</>
),
});
}
});
},
});
Expand Down
20 changes: 19 additions & 1 deletion app/(authenticated)/calendar/[eventID]/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,25 @@ export async function reinstateEvent(eventID: number) {
};
}

await Calendar.reinstateEvent(eventID);
const result = await Calendar.reinstateEvent(eventID);
if (!result.ok) {
switch (result.error) {
case "kit_clash":
return {
ok: false,
errors: {
root: "Reinstating this production would result in a kit clash. Please contact the Tech Team.",
},
};
default:
return {
ok: false,
errors: {
root: "An unknown error occurred (" + result.error + ")",
},
};
};
}

revalidatePath(`/calendar/${event.event_id}`);
revalidatePath("/calendar");
Expand Down
9 changes: 9 additions & 0 deletions features/calendar/adamRMS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export async function addProjectToAdamRMS(
event.end_date,
"deliver_dates",
);
await AdamRMS.setProjectStatus(
projectId,
// No idea why you'd link a cancelled project to the RMS, but you do you...
event.is_cancelled ? "cancelled" : event.is_tentative ? "tentative" : "confirmed",
);
await prisma.event.update({
where: {
event_id: eventID,
Expand Down Expand Up @@ -85,6 +90,10 @@ export async function linkAdamRMS(eventID: number, projectID: number) {
event.end_date,
"dates",
);
await AdamRMS.setProjectStatus(
projectID,
event.is_cancelled ? "cancelled" : event.is_tentative ? "tentative" : "confirmed",
);
await prisma.event.update({
where: {
event_id: eventID,
Expand Down
57 changes: 46 additions & 11 deletions features/calendar/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ export async function updateEvent(
// We use a raw query to get the event info so that we can SELECT FOR UPDATE,
// otherwise this risks a race condition.
const events = await $db.$queryRaw<
{ adam_rms_project_id: number | null; start_date: Date; end_date: Date }[]
{ adam_rms_project_id: number | null; start_date: Date; end_date: Date; is_tentative: boolean; is_cancelled: boolean; }[]
>`
SELECT adam_rms_project_id, start_date, end_date FROM events WHERE event_id = ${eventID} FOR UPDATE`;
SELECT adam_rms_project_id, start_date, end_date, is_tentative, is_cancelled FROM events WHERE event_id = ${eventID} FOR UPDATE`;
if (events.length === 0) {
throw new Error("Event not found");
}
Expand All @@ -207,6 +207,8 @@ export async function updateEvent(
// This is because AdamRMS will reject the request if the dates would cause a kit clash.
// We change the "deliver dates" first, because this actually triggers the kit clash check.
// Then if it succeeds we update it locally and update the event dates to match.
// Similarly, if the status has changed, we need to update that, as un-cancelling an event
// can have the same effect.
if (
event.adam_rms_project_id &&
(event.start_date.getTime() !== data.start_date.getTime() ||
Expand All @@ -222,6 +224,13 @@ export async function updateEvent(
return { ok: false, error: "kit_clash" };
}
}

// It's not possible to reinstate an even through this function, so we don't need to worry about
// the status changing from cancelled to uncancelled. In other words, cancelled trumps tentative.
if (!event.is_cancelled && event.adam_rms_project_id && event.is_tentative !== data.is_tentative) {
await AdamRMS.setProjectStatus(event.adam_rms_project_id, data.is_tentative ? "tentative" : "confirmed");
}

const result = await $db.event.update({
where: {
event_id: eventID,
Expand Down Expand Up @@ -288,25 +297,51 @@ export async function updateEventAttendeeStatus(
}

export async function cancelEvent(eventID: number) {
await prisma.event.update({
const res = await prisma.event.update({
where: {
event_id: eventID,
},
data: {
is_cancelled: true,
},
select: {
adam_rms_project_id: true,
}
});
if (res.adam_rms_project_id) {
await AdamRMS.setProjectStatus(res.adam_rms_project_id, "cancelled");
}
}

export async function reinstateEvent(eventID: number) {
await prisma.event.update({
where: {
event_id: eventID,
},
data: {
is_cancelled: false,
},
});
// Watch out! Cancelling an event in AdamRMS releases its assets, so un-cancelling
// an event can cause a kit clash. We need to check this first.
// Do it inside a transaction with a SELECT FOR UPDATE.
return await prisma.$transaction(async $db => {
const events = await $db.$queryRaw<{ adam_rms_project_id: number | null; }[]>`
SELECT adam_rms_project_id FROM events WHERE event_id = ${eventID} FOR UPDATE`;
if (events.length === 0) {
throw new Error("Event not found");
}
const event = events[0];

if (event.adam_rms_project_id) {
const changed = await AdamRMS.setProjectStatus(event.adam_rms_project_id, "confirmed");
if (!changed) {
return { ok: false, error: "kit_clash" };
}
}

await $db.event.update({
where: {
event_id: eventID,
},
data: {
is_cancelled: false,
},
});
return { ok: true };
})
}

export async function deleteEvent(eventID: number, userID: number) {
Expand Down
27 changes: 27 additions & 0 deletions lib/adamrms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,30 @@ export async function newQuickProjectComment(
text: comment,
})) as {};
}

export async function setProjectStatus(projectID: number, status: "tentative" | "confirmed" | "cancelled") {
let statusIDStr;
switch (status) {
case "tentative":
statusIDStr = process.env.ADAMRMS_TENTATIVE_STATUS_ID;
break;
case "confirmed":
statusIDStr = process.env.ADAMRMS_CONFIRMED_STATUS_ID;
break;
case "cancelled":
statusIDStr = process.env.ADAMRMS_CANCELLED_STATUS_ID;
break;
default:
invariant(false, `Invalid project status ${status}`);;
}
invariant(statusIDStr, "Missing status ID for status " + status);
const statusID = parseInt(statusIDStr, 10);

// We still need to check for kit clashes - it's possible that changing a cancelled
// project to un-cancelled will cause a clash.
const res = await makeRequest("/projects/changeStatus.php", "POST", {
projects_id: projectID.toString(10),
projects_status: statusID.toString(10),
}) as { changed: boolean };
return res.changed;
}