import { Body, Controller, Delete, Get, Path, Post, Put, Query, Request, Route, Security, Tags, } from "tsoa"; import { RequestWithUser } from "../interfaces/user"; import prisma from "../db"; import { Prisma, Status } from "@prisma/client"; import { branchRelationPermInclude, createPermCheck, createPermCondition, } from "../services/permission"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { notFoundError } from "../utils/error"; import { filterStatus } from "../services/prisma"; import { queryOrNot, whereDateQuery } from "../utils/relation"; type WorkflowPayload = { name: string; step: { id?: string; name: string; detail?: string | null; type?: string | null; value?: string[] | null; attributes?: { [key: string]: any }; responsiblePersonId?: string[]; responsibleInstitution?: string[]; responsibleGroup?: string[]; messengerByArea?: boolean; }[]; registeredBranchId?: string; status?: Status; }; const MANAGE_ROLES = [ "system", "head_of_admin", "admin", "executive", "accountant", "branch_admin", "branch_manager", "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCondCompany = createPermCondition(globalAllow); const permissionCheckCompany = createPermCheck(globalAllow); @Route("api/v1/workflow-template") @Tags("Workflow") export class FlowTemplateController extends Controller { @Get() @Security("keycloak") async getFlowTemplate( @Request() req: RequestWithUser, @Query() page: number = 1, @Query() pageSize: number = 30, @Query() status?: Status, @Query() query = "", @Query() activeOnly?: boolean, @Query() startDate?: Date, @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ { name: { contains: query, mode: "insensitive" } }, { step: { some: { name: { contains: query, mode: "insensitive" } }, }, }, ]), AND: { ...filterStatus(activeOnly ? Status.ACTIVE : status), registeredBranch: { OR: permissionCondCompany(req.user, { activeOnly: true }), }, }, ...whereDateQuery(startDate, endDate), } satisfies Prisma.WorkflowTemplateWhereInput; const [result, total] = await prisma.$transaction([ prisma.workflowTemplate.findMany({ where, include: { step: { include: { value: true, responsiblePerson: { include: { user: true }, }, responsibleInstitution: true, responsibleGroup: true, }, orderBy: { order: "asc" }, }, }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], take: pageSize, skip: (page - 1) * pageSize, }), prisma.workflowTemplate.count({ where }), ]); return { result: result.map((r) => ({ ...r, step: r.step.map((v) => ({ ...v, responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group), responsibleGroup: v.responsibleGroup.map((group) => group.group), })), })), page, pageSize, total, }; } @Get("{templateId}") @Security("keycloak") async getFlowTemplateById(@Request() _req: RequestWithUser, @Path() templateId: string) { const record = await prisma.workflowTemplate.findFirst({ include: { step: { orderBy: { order: "asc" }, include: { value: true, responsiblePerson: { include: { user: true }, }, responsibleInstitution: true, responsibleGroup: true, }, }, }, where: { id: templateId }, orderBy: { createdAt: "asc" }, }); if (!record) throw notFoundError("FlowTemplate"); return { ...record, step: record.step.map((v) => ({ ...v, responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group), responsibleGroup: v.responsibleGroup.map((group) => group.group), })), }; } @Post() @Security("keycloak", MANAGE_ROLES) async createFlowTemplate(@Request() req: RequestWithUser, @Body() body: WorkflowPayload) { const where = { OR: [ { name: { contains: body.name } }, { step: { some: { name: { contains: body.name } }, }, }, ], AND: { registeredBranch: { OR: permissionCondCompany(req.user), }, }, } satisfies Prisma.WorkflowTemplateWhereInput; const exists = await prisma.workflowTemplate.findFirst({ where }); if (exists) { throw new HttpError( HttpStatus.BAD_REQUEST, "Workflow template with this name already exists", "sameNameExists", ); } const userAffiliatedBranch = await prisma.branch.findFirst({ include: branchRelationPermInclude(req.user), where: body.registeredBranchId ? { id: body.registeredBranchId } : { user: { some: { userId: req.user.sub } }, }, }); if (!userAffiliatedBranch) { throw new HttpError( HttpStatus.BAD_REQUEST, "You must be affilated with at least one branch or specify branch to be registered (System permission required).", "reqMinAffilatedBranch", ); } await permissionCheckCompany(req.user, userAffiliatedBranch); return await prisma.workflowTemplate.create({ data: { ...body, registeredBranchId: userAffiliatedBranch.id, step: { create: body.step.map((v, i) => ({ type: v.type, value: { create: v.value?.map((val) => ({ value: val, })), }, name: v.name, detail: v.detail, order: i + 1, attributes: v.attributes, messengerByArea: v.messengerByArea, responsiblePerson: { create: v.responsiblePersonId?.map((id) => ({ userId: id, })), }, responsibleInstitution: { create: v.responsibleInstitution?.map((group) => ({ group })), }, responsibleGroup: { create: v.responsibleGroup?.map((group) => ({ group })), }, })), }, }, }); } @Put("{templateId}") @Security("keycloak", MANAGE_ROLES) async updateFlowTemplate( @Request() req: RequestWithUser, @Path() templateId: string, @Body() body: WorkflowPayload, ) { const record = await prisma.workflowTemplate.findUnique({ where: { id: templateId }, include: { registeredBranch: { include: branchRelationPermInclude(req.user), }, }, }); if (!record) throw notFoundError("Workflow"); await permissionCheckCompany(req.user, record.registeredBranch); return await prisma.workflowTemplate.update({ where: { id: templateId }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), step: { deleteMany: { id: { notIn: body.step.flatMap((v) => v.id || []) }, }, upsert: body.step.map((v, i) => ({ where: { id: v.id || "" }, create: { type: v.type, name: v.name, detail: v.detail, order: i + 1, attributes: v.attributes, value: { create: v.value?.map((val) => ({ value: val })), }, messengerByArea: v.messengerByArea, responsiblePerson: { createMany: { data: v.responsiblePersonId?.map((id) => ({ userId: id })) || [], skipDuplicates: true, }, }, id: undefined, }, update: { type: v.type, name: v.name, detail: v.detail, order: i + 1, attributes: v.attributes, value: { deleteMany: {}, create: v.value?.map((val) => ({ value: val })), }, messengerByArea: v.messengerByArea, responsiblePerson: v.responsiblePersonId ? { deleteMany: { userId: { notIn: v.responsiblePersonId }, }, createMany: { data: v.responsiblePersonId?.map((id) => ({ userId: id })) || [], skipDuplicates: true, }, } : undefined, responsibleInstitution: { deleteMany: {}, create: v.responsibleInstitution?.map((group) => ({ group })), }, responsibleGroup: { deleteMany: {}, create: v.responsibleGroup?.map((group) => ({ group })), }, }, })), }, }, }); } @Delete("{templateId}") @Security("keycloak", MANAGE_ROLES) async deleteFlowTemplateById(@Request() req: RequestWithUser, @Path() templateId: string) { const record = await prisma.workflowTemplate.findUnique({ where: { id: templateId }, include: { registeredBranch: { include: branchRelationPermInclude(req.user), }, }, }); if (!record) throw notFoundError("Workflow"); await permissionCheckCompany(req.user, record.registeredBranch); return await prisma.workflowTemplate.delete({ where: { id: templateId }, }); } }