From ac539332e60ed2eff6f0b7a2b5aeb561aeb061ce Mon Sep 17 00:00:00 2001 From: Joao Correia Date: Sat, 1 Feb 2025 22:36:42 +0000 Subject: [PATCH] major change on how workflow builder works. It now fetches in edit mode all the currently configured workflows --- .../ApprovalWorkflows/WorkflowForm.tsx | 1 + src/interfaces/approval.workflow.ts | 4 + .../api/approval-workflows/[id]/clone.ts | 4 +- src/pages/api/approval-workflows/[id]/edit.ts | 4 +- .../api/approval-workflows/[id]/index.ts | 6 +- src/pages/api/approval-workflows/create.ts | 17 +++- src/pages/approval-workflows/[id]/clone.tsx | 9 ++- src/pages/approval-workflows/[id]/edit.tsx | 11 ++- src/pages/approval-workflows/[id]/index.tsx | 48 +++++++++-- src/pages/approval-workflows/create.tsx | 18 +++-- src/pages/approval-workflows/index.tsx | 14 ++-- src/utils/approval.workflows.be.ts | 79 +++++++++++++++---- 12 files changed, 159 insertions(+), 56 deletions(-) diff --git a/src/components/ApprovalWorkflows/WorkflowForm.tsx b/src/components/ApprovalWorkflows/WorkflowForm.tsx index ffa92404..8cc4686d 100644 --- a/src/components/ApprovalWorkflows/WorkflowForm.tsx +++ b/src/components/ApprovalWorkflows/WorkflowForm.tsx @@ -33,6 +33,7 @@ export default function WorkflowForm({ workflow, onWorkflowChange, entityApprove key: stepCounter, stepType: "approval-by", stepNumber: workflow.steps.length, + completed: false, assignees: [null], firstStep: false, finalStep: false, diff --git a/src/interfaces/approval.workflow.ts b/src/interfaces/approval.workflow.ts index f5563e58..69cffa5e 100644 --- a/src/interfaces/approval.workflow.ts +++ b/src/interfaces/approval.workflow.ts @@ -43,6 +43,10 @@ export interface EditableWorkflowStep { key: number, stepType: StepType, stepNumber: number, + completed: boolean, + rejected?: boolean, + completedBy?: User["id"], + completedDate?: number, assignees: (User["id"] | null | undefined)[]; // bit of an hack, but allowing null or undefined values allows us to match one to one the select input components with the assignees array. And since select inputs allow undefined or null values, it is allowed here too, but must validate required input before form submission firstStep: boolean, finalStep?: boolean, diff --git a/src/pages/api/approval-workflows/[id]/clone.ts b/src/pages/api/approval-workflows/[id]/clone.ts index 6515d751..be0ee1b8 100644 --- a/src/pages/api/approval-workflows/[id]/clone.ts +++ b/src/pages/api/approval-workflows/[id]/clone.ts @@ -2,7 +2,7 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; -import { createApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { createConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; @@ -23,5 +23,5 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const approvalWorkflow: ApprovalWorkflow = req.body; if (approvalWorkflow) - return res.status(201).json(await createApprovalWorkflow(approvalWorkflow)); + return res.status(201).json(await createConfiguredWorkflow(approvalWorkflow)); } diff --git a/src/pages/api/approval-workflows/[id]/edit.ts b/src/pages/api/approval-workflows/[id]/edit.ts index c61e200a..39fc607f 100644 --- a/src/pages/api/approval-workflows/[id]/edit.ts +++ b/src/pages/api/approval-workflows/[id]/edit.ts @@ -2,7 +2,7 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; -import { updateApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { updateConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; @@ -24,7 +24,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) { const approvalWorkflow: ApprovalWorkflow = req.body; if (id && approvalWorkflow) { - await updateApprovalWorkflow(id, approvalWorkflow); + await updateConfiguredWorkflow(approvalWorkflow); return res.status(204).end(); } } diff --git a/src/pages/api/approval-workflows/[id]/index.ts b/src/pages/api/approval-workflows/[id]/index.ts index e8de49a8..53ce6f21 100644 --- a/src/pages/api/approval-workflows/[id]/index.ts +++ b/src/pages/api/approval-workflows/[id]/index.ts @@ -2,7 +2,7 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; -import { deleteApprovalWorkflow, updateApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { deleteConfiguredWorkflow, updateConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; @@ -23,7 +23,7 @@ async function del(req: NextApiRequest, res: NextApiResponse) { const { id } = req.query as { id?: string }; - if (id) return res.status(200).json(await deleteApprovalWorkflow(id)); + if (id) return res.status(200).json(await deleteConfiguredWorkflow(id)); } async function put(req: NextApiRequest, res: NextApiResponse) { @@ -38,7 +38,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) { const workflow = req.body; if (id && workflow) { - await updateApprovalWorkflow(id, workflow); + await updateConfiguredWorkflow(workflow); return res.status(204).end(); } } diff --git a/src/pages/api/approval-workflows/create.ts b/src/pages/api/approval-workflows/create.ts index dab8e80f..cbdddb02 100644 --- a/src/pages/api/approval-workflows/create.ts +++ b/src/pages/api/approval-workflows/create.ts @@ -1,13 +1,19 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; +import { Entity } from "@/interfaces/entity"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; -import { createApprovalWorkflows } from "@/utils/approval.workflows.be"; +import { replaceConfiguredWorkflowsByEntities } from "@/utils/approval.workflows.be"; import { withIronSessionApiRoute } from "iron-session/next"; import type { NextApiRequest, NextApiResponse } from "next"; export default withIronSessionApiRoute(handler, sessionOptions); +interface ReplaceApprovalWorkflowsRequest { + filteredWorkflows: ApprovalWorkflow[]; + userEntitiesWithLabel: Entity[]; +} + async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === "POST") return await post(req, res); } @@ -20,9 +26,12 @@ async function post(req: NextApiRequest, res: NextApiResponse) { return res.status(403).json({ ok: false }); } - const approvalWorkflows: ApprovalWorkflow[] = req.body; + const { filteredWorkflows, userEntitiesWithLabel } = req.body as ReplaceApprovalWorkflowsRequest; - await createApprovalWorkflows(approvalWorkflows); + const configuredWorkflows: ApprovalWorkflow[] = filteredWorkflows; + const entitiesIds: string[] = userEntitiesWithLabel.map((e) => e.id); - return res.status(201).json(approvalWorkflows); + await replaceConfiguredWorkflowsByEntities(configuredWorkflows, entitiesIds); + + return res.status(201).json({ ok: true }); } diff --git a/src/pages/approval-workflows/[id]/clone.tsx b/src/pages/approval-workflows/[id]/clone.tsx index 8baaaf36..b42293a1 100644 --- a/src/pages/approval-workflows/[id]/clone.tsx +++ b/src/pages/approval-workflows/[id]/clone.tsx @@ -22,9 +22,9 @@ import { useEffect, useState } from "react"; import { BsChevronLeft } from "react-icons/bs"; import { GrClearOption } from "react-icons/gr"; import { toast, ToastContainer } from "react-toastify"; -import { getApprovalWorkflow } from "@/utils/approval.workflows.be"; import { useRouter } from "next/router"; import axios from "axios"; +import { getConfiguredWorkflow } from "@/utils/approval.workflows.be"; export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { const user = await requestUser(req, res); @@ -35,7 +35,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } const { id } = params as { id: string }; - const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id); + const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id); if (!workflow) return redirect("/approval-workflows") @@ -91,6 +91,7 @@ export default function Home({ user, workflow, userEntitiesWithLabel, userEntiti key: step.stepNumber + 999, // just making sure they are unique because new steps that users add will have key=3 key=4 etc stepType: step.stepType, stepNumber: step.stepNumber, + completed: false, assignees: step.assignees.map(id => id), firstStep: step.firstStep || false, finalStep: step.finalStep || false, @@ -179,8 +180,8 @@ export default function Home({ user, workflow, userEntitiesWithLabel, userEntiti startDate: Date.now(), status: "pending", steps: [ - { key: 9998, stepType: "form-intake", stepNumber: 1, firstStep: true, finalStep: false, assignees: [null] }, - { key: 9999, stepType: "approval-by", stepNumber: 2, firstStep: false, finalStep: true, assignees: [null] }, + { key: 9998, stepType: "form-intake", stepNumber: 1, completed: false, firstStep: true, finalStep: false, assignees: [null] }, + { key: 9999, stepType: "approval-by", stepNumber: 2, completed: false, firstStep: false, finalStep: true, assignees: [null] }, ], }; setCloneWorkflow(newWorkflow); diff --git a/src/pages/approval-workflows/[id]/edit.tsx b/src/pages/approval-workflows/[id]/edit.tsx index 1677f3ca..185cc655 100644 --- a/src/pages/approval-workflows/[id]/edit.tsx +++ b/src/pages/approval-workflows/[id]/edit.tsx @@ -8,18 +8,18 @@ import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User } import { sessionOptions } from "@/lib/session"; import { redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; +import { getConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { getEntityUsers } from "@/utils/users.be"; +import axios from "axios"; import { motion } from "framer-motion"; import { withIronSessionSsr } from "iron-session/next"; import Head from "next/head"; import Link from "next/link"; +import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { BsChevronLeft } from "react-icons/bs"; import { toast, ToastContainer } from "react-toastify"; -import axios from "axios"; -import { getApprovalWorkflow } from "@/utils/approval.workflows.be"; -import { useRouter } from "next/router"; export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { const user = await requestUser(req, res); @@ -30,7 +30,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } const { id } = params as { id: string }; - const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id); + const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id); if (!workflow) return redirect("/approval-workflows") @@ -62,6 +62,9 @@ export default function Home({ user, workflow, workflowEntityApprovers }: Props) key: step.stepNumber + 999, // just making sure they are unique because new steps that users add will have key=3 key=4 etc stepType: step.stepType, stepNumber: step.stepNumber, + completed: step.completed, + completedBy: step.completedBy || undefined, + completedDate: step.completedDate || undefined, assignees: step.assignees.map(id => id), firstStep: step.firstStep || false, finalStep: step.finalStep || false, diff --git a/src/pages/approval-workflows/[id]/index.tsx b/src/pages/approval-workflows/[id]/index.tsx index a510f033..12d79a90 100644 --- a/src/pages/approval-workflows/[id]/index.tsx +++ b/src/pages/approval-workflows/[id]/index.tsx @@ -11,7 +11,7 @@ import { User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; import { redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; -import { getApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { getConfiguredWorkflow } from "@/utils/approval.workflows.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { getSpecificUsers, getUser } from "@/utils/users.be"; import axios from "axios"; @@ -23,12 +23,14 @@ import { useRouter } from "next/router"; import { useState } from "react"; import { BsChevronLeft } from "react-icons/bs"; import { FaSpinner, FaWpforms } from "react-icons/fa6"; +import { FiSave } from "react-icons/fi"; +import { IoMdCheckmarkCircleOutline } from "react-icons/io"; +import { IoDocumentTextOutline } from "react-icons/io5"; import { MdOutlineDoubleArrow } from "react-icons/md"; import { RiThumbUpLine } from "react-icons/ri"; -import { toast, ToastContainer } from "react-toastify"; -import { IoMdCheckmarkCircleOutline } from "react-icons/io"; -import { FiSave } from "react-icons/fi"; import { RxCrossCircled } from "react-icons/rx"; +import { TiEdit } from "react-icons/ti"; +import { toast, ToastContainer } from "react-toastify"; export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }) => { const user = await requestUser(req, res); @@ -39,7 +41,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } const { id } = params as { id: string }; - const workflow: ApprovalWorkflow | null = await getApprovalWorkflow(id); + const workflow: ApprovalWorkflow | null = await getConfiguredWorkflow(id); if (!workflow) return redirect("/approval-workflows") @@ -132,6 +134,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques const updatedWorkflow: ApprovalWorkflow = { ...workflow, + status: selectedStepIndex === workflow.steps.length - 1 ? "approved" : "pending", steps: workflow.steps.map((step, index) => index === selectedStepIndex ? { @@ -206,6 +209,14 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques }) }; + const handleViewExam = () => { + + } + + const handleEditExam = () => { + + } + return ( <> @@ -244,6 +255,29 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques status={workflow.status} /> +
+ + + +
{steps.find((step) => !step.completed) === undefined && } @@ -271,7 +305,7 @@ export default function Home({ user, workflow, workflowAssignees, workflowReques {/* Side panel */} -
+
{isPanelOpen && selectedStep && ( - Reject Step + Reject )} diff --git a/src/pages/approval-workflows/create.tsx b/src/pages/approval-workflows/create.tsx index bedc3014..999e971c 100644 --- a/src/pages/approval-workflows/create.tsx +++ b/src/pages/approval-workflows/create.tsx @@ -10,6 +10,7 @@ import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User } import { sessionOptions } from "@/lib/session"; import { redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; +import { getConfiguredWorkflowsByEntities } from "@/utils/approval.workflows.be"; import { getEntities } from "@/utils/entities.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { getEntitiesUsers } from "@/utils/users.be"; @@ -33,10 +34,13 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { return redirect("/") const userEntitiesWithLabel = await getEntities(user.entities.map(entity => entity.id)); + + const allConfiguredWorkflows = await getConfiguredWorkflowsByEntities(userEntitiesWithLabel.map(entity => entity.id)); return { props: serialize({ user, + allConfiguredWorkflows, userEntitiesWithLabel, userEntitiesApprovers: await getEntitiesUsers(userEntitiesWithLabel.map(entity => entity.id), { type: { $in: ["teacher", "corporate", "mastercorporate", "developer"] } }) as (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[], }), @@ -45,12 +49,13 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { interface Props { user: User, + allConfiguredWorkflows: EditableApprovalWorkflow[], userEntitiesWithLabel: Entity[], userEntitiesApprovers: (TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[], } -export default function Home({ user, userEntitiesWithLabel, userEntitiesApprovers }: Props) { - const [workflows, setWorkflows] = useState([]); +export default function Home({ user, allConfiguredWorkflows, userEntitiesWithLabel, userEntitiesApprovers }: Props) { + const [workflows, setWorkflows] = useState(allConfiguredWorkflows); const [selectedWorkflowId, setSelectedWorkflowId] = useState(undefined); const [entityId, setEntityId] = useState(null); const [entityApprovers, setEntityApprovers] = useState<(TeacherUser | CorporateUser | MasterCorporateUser | DeveloperUser)[]>([]); @@ -93,13 +98,14 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesApprover ...workflow, steps: workflow.steps.map(step => ({ ...step, - completed: false, assignees: step.assignees.filter((assignee): assignee is string => assignee !== null && assignee !== undefined) })) })); + const requestData = {filteredWorkflows, userEntitiesWithLabel}; + axios - .post(`/api/approval-workflows/create`, filteredWorkflows) + .post(`/api/approval-workflows/create`, requestData) .then(() => { toast.success("Approval Workflows created successfully."); setIsRedirecting(true); @@ -135,8 +141,8 @@ export default function Home({ user, userEntitiesWithLabel, userEntitiesApprover startDate: Date.now(), status: "pending", steps: [ - { key: 9998, stepType: "form-intake", stepNumber: 1, firstStep: true, finalStep: false, assignees: [null] }, - { key: 9999, stepType: "approval-by", stepNumber: 2, firstStep: false, finalStep: true, assignees: [null] }, + { key: 9998, stepType: "form-intake", stepNumber: 1, completed: false, firstStep: true, finalStep: false, assignees: [null] }, + { key: 9999, stepType: "approval-by", stepNumber: 2, completed: false, firstStep: false, finalStep: true, assignees: [null] }, ], }; setWorkflows((prev) => [...prev, newWorkflow]); diff --git a/src/pages/approval-workflows/index.tsx b/src/pages/approval-workflows/index.tsx index 144e3447..b91ac6ef 100644 --- a/src/pages/approval-workflows/index.tsx +++ b/src/pages/approval-workflows/index.tsx @@ -1,5 +1,6 @@ import Layout from "@/components/High/Layout"; import Button from "@/components/Low/Button"; +import Input from "@/components/Low/Input"; import Select from "@/components/Low/Select"; import { Module, ModuleTypeLabels } from "@/interfaces"; import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel } from "@/interfaces/approval.workflow"; @@ -8,6 +9,7 @@ import { User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; import { redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; +import { getConfiguredWorkflows } from "@/utils/approval.workflows.be"; import { getEntities } from "@/utils/entities.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { getSpecificUsers } from "@/utils/users.be"; @@ -17,18 +19,14 @@ import clsx from "clsx"; import { withIronSessionSsr } from "iron-session/next"; import Head from "next/head"; import Link from "next/link"; +import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { BsTrash } from "react-icons/bs"; import { FaRegEdit } from "react-icons/fa"; +import { FaRegClone } from "react-icons/fa6"; import { IoIosAddCircleOutline } from "react-icons/io"; import { toast, ToastContainer } from "react-toastify"; -import Input from "@/components/Low/Input"; -import { FaRegClone } from "react-icons/fa6"; -import useApprovalWorkflows from "@/hooks/useApprovalWorkflows"; -import { getApprovalWorkflows } from "@/utils/approval.workflows.be"; -import { useRouter } from "next/router"; - const columnHelper = createColumnHelper(); export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { @@ -38,7 +36,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) return redirect("/") - const workflows = await getApprovalWorkflows(); + const workflows = await getConfiguredWorkflows(); const allAssigneeIds: string[] = [ ...new Set( @@ -331,7 +329,7 @@ export default function ApprovalWorkflows({ user, workflows, workflowsAssignees, className="min-w-fit text-lg font-medium flex items-center gap-2 text-left" > - Configure New Workflows + Configure Workflows diff --git a/src/utils/approval.workflows.be.ts b/src/utils/approval.workflows.be.ts index f82056dd..6167385a 100644 --- a/src/utils/approval.workflows.be.ts +++ b/src/utils/approval.workflows.be.ts @@ -1,36 +1,83 @@ -import { ObjectId } from "mongodb"; import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import client from "@/lib/mongodb"; +import { ObjectId } from "mongodb"; const db = client.db(process.env.MONGODB_DB); -export const getApprovalWorkflows = async (ids?: string[]) => { +export const getConfiguredWorkflows = async (ids?: string[]) => { return await db - .collection("approval-workflows") + .collection("configured-workflows") .find(ids ? { _id: { $in: ids.map((id) => new ObjectId(id)) } } : {}) .toArray(); }; -export const getApprovalWorkflow = async (id: string) => { - return await db.collection("approval-workflows").findOne({ _id: new ObjectId(id) }); +export const getConfiguredWorkflow = async (id: string) => { + return await db.collection("configured-workflows").findOne({ _id: new ObjectId(id) }); }; -export const createApprovalWorkflow = async (workflow: ApprovalWorkflow) => { - const { _id, ...workflowWithoutId } = workflow as ApprovalWorkflow; - return await db.collection("approval-workflows").insertOne(workflowWithoutId); +export const getConfiguredWorkflowsByEntities = async (ids: string[]) => { + return await db + .collection("configured-workflows") + .find({ entityId: { $in: ids } }) + .toArray(); }; -export const createApprovalWorkflows = async (workflows: ApprovalWorkflow[]) => { +export const createConfiguredWorkflow = async (workflow: ApprovalWorkflow) => { + const { _id, ...workflowWithoutId } = workflow as ApprovalWorkflow; + return await db.collection("configured-workflows").insertOne(workflowWithoutId); +}; + +export const createConfiguredWorkflows = async (workflows: ApprovalWorkflow[]) => { if (workflows.length === 0) return; - const workflowsWithoutIds: ApprovalWorkflow[] = workflows.map(({_id, ...wfs}) => wfs) - return await db.collection("approval-workflows").insertMany(workflowsWithoutIds); + const workflowsWithoutIds: ApprovalWorkflow[] = workflows.map(({ _id, ...wfs }) => wfs); + return await db.collection("configured-workflows").insertMany(workflowsWithoutIds); }; -export const updateApprovalWorkflow = async (id: string, workflow: ApprovalWorkflow) => { - const { _id, ...workflowWithoutId } = workflow as ApprovalWorkflow; - return await db.collection("approval-workflows").replaceOne({ _id: new ObjectId(id) }, workflowWithoutId); +export const updateConfiguredWorkflow = async (workflow: ApprovalWorkflow) => { + const { _id, ...workflowWithoutId } = workflow as ApprovalWorkflow; + return await db.collection("configured-workflows").replaceOne({ _id: new ObjectId(_id) }, workflowWithoutId); }; -export const deleteApprovalWorkflow = async (id: string) => { - return await db.collection("approval-workflows").deleteOne({ _id: new ObjectId(id) }); +export const updateConfiguredWorkflows = async (workflows: ApprovalWorkflow[]) => { + const bulkOperations = workflows.map((workflow) => { + const { _id, ...workflowWithoutId } = workflow; + return { + replaceOne: { + filter: { _id: new ObjectId(_id) }, + replacement: workflowWithoutId, + }, + }; + }); + + return await db.collection("configured-workflows").bulkWrite(bulkOperations); +}; + +export const deleteConfiguredWorkflow = async (id: string) => { + return await db.collection("configured-workflows").deleteOne({ _id: new ObjectId(id) }); +}; + +export const replaceConfiguredWorkflowsByEntities = async (workflows: ApprovalWorkflow[], entityIds: string[]) => { + // 1. Keep track of the _id values of all workflows we want to end up with + const finalIds = new Set(); + + // 2. Process incoming workflows + for (const workflow of workflows) { + if (workflow._id) { + // Replace the existing ones + await updateConfiguredWorkflow(workflow); + finalIds.add(workflow._id.toString()); + } else { + // Insert if no _id + const insertResult = await createConfiguredWorkflow(workflow); + finalIds.add(insertResult.insertedId.toString()); + } + } + + // 3. Delete any existing workflow (within these entityIds) that wasn't in the final list + await db.collection("configured-workflows").deleteMany({ + _id: { + $nin: Array.from(finalIds).map((id) => new ObjectId(id)), + }, + entityId: { $in: entityIds }, + }); };