File

src/compliance/risk.service.ts

Index

Methods

Constructor

constructor(prismaService: PrismaService, temporalProvider: TemporalProvider, notificationService: NotificationService, requestService: RequestService)
Parameters :
Name Type Optional
prismaService PrismaService No
temporalProvider TemporalProvider No
notificationService NotificationService No
requestService RequestService No

Methods

Async assignRiskToUser
assignRiskToUser(riskId: number, userId: string, assignedBy: string)

Assign a compliance risk to a user

Parameters :
Name Type Optional Description
riskId number No

risk id

userId string No

user id

assignedBy string No

user id

Returns : unknown

assigned

Async attachRequestToRiskSubmission
attachRequestToRiskSubmission(id: number, requestId: number)

Attach a request to a compliance risk submission

Parameters :
Name Type Optional Description
id number No

submission id

requestId number No

request id

Returns : unknown

updated submission

Async createComplianceRisk
createComplianceRisk(complianceRisk: ComplianceRiskDto)

create a new compliance risk

Parameters :
Name Type Optional Description
complianceRisk ComplianceRiskDto No

compliance risk to create

created risk

Async createComplianceRisks
createComplianceRisks(submissionId: number, complianceRisks: ComplianceRiskCreateInput[])

create multiple compliance risks

Parameters :
Name Type Optional Description
submissionId number No

submission id

complianceRisks ComplianceRiskCreateInput[] No

list of risks to create

list of created risks

Async createNotification
createNotification(event: ComplianceEvents, channel: NotificationChannel, risk: ComplianceRisk, assignedBy: string)

Create a notification for a given compliance risk and event

Parameters :
Name Type Optional Description
event ComplianceEvents No

event

channel NotificationChannel No

channel

risk ComplianceRisk No

compliance risk

assignedBy string No

user id

Returns : unknown

notification

Async deleteComplianceRisk
deleteComplianceRisk(id: number)

Delete a compliance risk

Parameters :
Name Type Optional Description
id number No

risk id

deleted risk

Async deleteDependentRisk
deleteDependentRisk(riskToDelete: ComplianceRisk[])

Function to delete dependent risks deletes risks and declines requests for the risks that are submitted

Parameters :
Name Type Optional
riskToDelete ComplianceRisk[] No
Returns : Promise<any>
Async deletePolicyDependentRisk
deletePolicyDependentRisk(policyId: number)

Function to delete policy dependent risks deletes risks and declines requests for the risks that are submitted

Parameters :
Name Type Optional
policyId number No
Returns : unknown
Async deleteRuleDependentRisk
deleteRuleDependentRisk(ruleIds: number[])

Function to delete rule dependent risks deletes risks and declines requests for the risks that are submitted

Parameters :
Name Type Optional
ruleIds number[] No
Returns : Promise<any>

Promise

Async findExisting
findExisting(where?: Prisma.ComplianceRiskWhereInput)

Find compliance risks for a submission

Parameters :
Name Type Optional
where Prisma.ComplianceRiskWhereInput Yes

list of compliance risks

Async findForProject
findForProject(projectId: number)

Find compliance risks for a project

Parameters :
Name Type Optional Description
projectId number No

project id

list of compliance risks

Async findMany
findMany(query: literal type)

Find compliance risks based on query

Parameters :
Name Type Optional Description
query literal type No

query object

Returns : unknown

list of compliance risks

Async findOne
findOne(id: number)

Find a compliance risk by id

Parameters :
Name Type Optional Description
id number No

risk id

compliance risk

Async findRiskSubmission
findRiskSubmission(id: number)

Find a compliance risk submission by id

Parameters :
Name Type Optional Description
id number No

submission id

Returns : Promise<ComplianceRiskSubmission>

compliance risk submission

getDependentProjects
getDependentProjects(ruleIds: number[])

Function to get dependent projects for a list of rule ids

Parameters :
Name Type Optional
ruleIds number[] No
Returns : Promise<number[]>

Promise<number[]> list of project ids

Async submitComplianceRisk
submitComplianceRisk(submissionData: ComplianceRiskSubmissionDto, uploadedBy: string)

Submit a compliance risk

Parameters :
Name Type Optional
submissionData ComplianceRiskSubmissionDto No
uploadedBy string No
Returns : unknown
Async updateComplianceRisk
updateComplianceRisk(id: number, complianceRisk: UpdateComplianceRiskDto)

Update a compliance risk

Parameters :
Name Type Optional Description
id number No

risk id

complianceRisk UpdateComplianceRiskDto No

risk to update

updated risk

Async updateRiskSubmission
updateRiskSubmission(id: number, status: ComplianceRiskSubmissionStatus, evaluatedBy?: string)

Update a compliance risk submission status

Parameters :
Name Type Optional Description
id number No

submission id

status ComplianceRiskSubmissionStatus No

status

evaluatedBy string Yes

user id

Returns : unknown

updated submission

import { Injectable } from "@nestjs/common";
import { PrismaService } from "../common/prisma/prisma.service";
import { ComplianceRisk } from "./entities/risk.entity";
import {
  ComplianceRiskStatus,
  ComplianceRiskSubmissionStatus,
} from "./entities/compliance.types";
import {
  ComplianceRiskCreateInput,
  ComplianceRiskDto,
  UpdateComplianceRiskDto,
} from "./dto/risk.dto";
import { ComplianceEvents, complianceEventsConfig } from "./compliance.events";
import {
  NotificationChannel,
  NotificationEntityType,
} from "../notification/types/notification.enums";
import {
  getNamesFromEmail,
  getTextFromTemplate,
  getUuidSlug,
} from "../common/helper";
import { EventType } from "../common/events";
import { NotificationService } from "../notification/notification.service";
import { TemporalProvider } from "../providers/temporal/temporal.provider";
import { ComplianceRiskSubmissionDto } from "./dto/risk.submission.dto";
import { ComplianceRiskSubmission } from "./entities/risk.submission.entity";
import { ProjectStages } from "../project/entities/project.entity";
import { RequestService } from "../request/request.service";
import { Prisma } from "@prisma/client";

@Injectable()
export class ComplianceRiskService {
  constructor(
    private readonly prismaService: PrismaService,
    private readonly temporalProvider: TemporalProvider,
    private readonly notificationService: NotificationService,
    private readonly requestService: RequestService,
  ) {}

  /**
   * create a new compliance risk
   * @param {ComplianceRiskDto} complianceRisk compliance risk to create
   * @returns {Promise<ComplianceRisk>} created risk
   */
  async createComplianceRisk(
    complianceRisk: ComplianceRiskDto,
  ): Promise<ComplianceRisk> {
    return new ComplianceRisk(
      await this.prismaService.complianceRisk.create({
        data: {
          assignedTo: complianceRisk.assignedTo,
          status: ComplianceRiskStatus.TODO,
          project: {
            connect: { id: complianceRisk.projectId },
          },
          complianceSubmission: {
            connect: { id: complianceRisk.complianceSubmissionId },
          },
          rule: {
            connect: { id: complianceRisk.ruleId },
          },
        },
      }),
    );
  }

  /**
   * create multiple compliance risks
   * @param {number} submissionId submission id
   * @param {ComplianceRiskCreateInput[]} complianceRisks list of risks to create
   * @returns {Promise<ComplianceRisk[]>} list of created risks
   */
  async createComplianceRisks(
    submissionId: number,
    complianceRisks: ComplianceRiskCreateInput[],
  ): Promise<ComplianceRisk[]> {
    await this.prismaService.complianceRisk.createMany({
      data: complianceRisks,
    });

    return this.prismaService.complianceRisk.findMany({
      where: {
        complianceSubmissionId: submissionId,
      },
      include: {
        submissions: true,
      },
    });
  }

  /**
   * Find a compliance risk by id
   * @param {number} id risk id
   * @returns {Promise<ComplianceRisk>} compliance risk
   */
  async findOne(id: number): Promise<ComplianceRisk> {
    const complianceRisk = await this.prismaService.complianceRisk.findUnique({
      where: { id: id },
      include: { project: true },
    });
    return new ComplianceRisk(complianceRisk);
  }

  /**
   * Find compliance risks for a project
   * @param {number} projectId project id
   * @returns {Promise<ComplianceRisk[]>} list of compliance risks
   */
  async findForProject(projectId: number): Promise<ComplianceRisk[]> {
    const complianceRisks = await this.prismaService.complianceRisk.findMany({
      where: {
        status: {
          notIn: [ComplianceRiskStatus.DELETED, ComplianceRiskStatus.ARCHIVED],
        },
        AND: { projectId: { equals: projectId } },
      },
      include: {
        submissions: true,
        rule: true,
      },
      orderBy: [{ createdAt: "desc" }],
    });

    return complianceRisks.map(
      (complianceRisk) => new ComplianceRisk(complianceRisk),
    );
  }

  /**
   * Find compliance risks for a submission
   * @param {object} query query object
   * @returns {Promise<ComplianceRisk[]>} list of compliance risks
   */
  async findExisting(
    where?: Prisma.ComplianceRiskWhereInput,
  ): Promise<ComplianceRisk[]> {
    const existing = await this.prismaService.complianceRisk.findMany({
      where: {
        ...where,
      },
      include: {
        rule: true,
        submissions: true,
      },
    });
    return existing.map((risk) => new ComplianceRisk(risk));
  }

  /**
   * Find compliance risks based on query
   * @param {object} query query object
   * @returns {Promise<ComplianceRisk[]>} list of compliance risks
   */
  async findMany(query: {
    assignedTo?: string | null;
    status?: ComplianceRiskStatus;
    projectId?: number;
    complianceSubmissionId?: number;
    approvers?: string[];
  }) {
    const whereInput = {
      assignedTo: query.assignedTo,
      status: query.status,
      projectId: query.projectId,
      complianceSubmissionId: query.complianceSubmissionId,
      approvers: undefined,
    };

    if (query.approvers && query.approvers.length > 0) {
      whereInput.approvers = {
        hasSome: query.approvers || [],
      };
    }

    return this.prismaService.complianceRisk.findMany({
      where: whereInput,
    });
  }

  /**
   * Update a compliance risk
   * @param {number} id risk id
   * @param {UpdateComplianceRiskDto} complianceRisk risk to update
   * @returns {Promise<ComplianceRisk>} updated risk
   */
  async updateComplianceRisk(
    id: number,
    complianceRisk: UpdateComplianceRiskDto,
  ): Promise<ComplianceRisk> {
    return new ComplianceRisk(
      await this.prismaService.complianceRisk.update({
        where: {
          id: id,
        },
        data: complianceRisk,
      }),
    );
  }

  /**
   * Delete a compliance risk
   * @param {number} id risk id
   * @returns {Promise<ComplianceRisk>} deleted risk
   */
  async deleteComplianceRisk(id: number): Promise<ComplianceRisk> {
    return this.updateComplianceRisk(id, {
      status: ComplianceRiskStatus.DELETED,
    });
  }

  /**
   * Assign a compliance risk to a user
   * @param {number} riskId risk id
   * @param {string} userId user id
   * @param {string} assignedBy user id
   * @returns {Promise<ComplianceRisk>} assigned
   */
  async assignRiskToUser(riskId: number, userId: string, assignedBy: string) {
    const _risk = await this.prismaService.complianceRisk.update({
      where: { id: riskId },
      data: {
        assignedTo: userId,
      },
      include: {
        project: true,
        rule: true,
      },
    });
    const risk = new ComplianceRisk(_risk);

    const notificationPromises = [];
    notificationPromises.push(
      this.createNotification(
        ComplianceEvents.COMPLIANCE_ASSIGN_RISK,
        NotificationChannel.APP,
        risk,
        assignedBy,
      ),
      this.createNotification(
        ComplianceEvents.COMPLIANCE_ASSIGN_RISK,
        NotificationChannel.EMAIL,
        risk,
        assignedBy,
      ),
    );

    const notifications = (await Promise.all(notificationPromises)).flat();

    notifications.map((notification) => {
      if (notification.channel != NotificationChannel.APP) {
        this.temporalProvider.runAsync(
          "deliverNotificationWorkflow",
          [notification.id],
          process.env.DEFAULT_QUEUE_NAME,
          `workflow-deliverNotificationWorkflow-${getUuidSlug()}`,
        );
      }
    });

    return risk;
  }

  /**
   * Create a notification for a given compliance risk and event
   * @param {ComplianceEvents} event event
   * @param {NotificationChannel} channel channel
   * @param {ComplianceRisk} risk compliance risk
   * @param {string} assignedBy user id
   * @returns {Promise<Notification>} notification
   */
  async createNotification(
    event: ComplianceEvents,
    channel: NotificationChannel,
    risk: ComplianceRisk,
    assignedBy: string,
  ) {
    const entityType = NotificationEntityType.COMPLIANCE_RISK;
    const entityId = risk.id;

    const templateVars = {
      sendToName: getNamesFromEmail(risk.assignedTo),
      sendToEmail: risk.assignedTo,
      assigner: getNamesFromEmail(assignedBy),
      project: risk.project,
      risk: risk,
      rule: risk.rule,
      complianceUrl: `${process.env.TURO_BASE_URL}projects/${risk.project["id"]}?activeTab=compliance`,
    };

    const config = complianceEventsConfig[event];
    const template = config.channelConfig[channel];

    const subject = getTextFromTemplate(template.subject, templateVars);
    const body = getTextFromTemplate(template.body, templateVars);

    const notificationData = {
      projectId: risk.project["id"],
      subject,
      body,
      severity: config.severity,
      channel,
      userId: risk.assignedTo,
      entityId,
      entityType,
      eventType: EventType.COMPLIANCE_RISK,
      eventIdentifier: event,
      // notifier: assignedBy,
    };

    return this.notificationService.create(notificationData);
  }

  /**
   * Submit a compliance risk
   * @param {ComplianceRiskSubmissionDto} submission risk submission
   */
  async submitComplianceRisk(
    submissionData: ComplianceRiskSubmissionDto,
    uploadedBy: string,
  ) {
    const submission = await this.prismaService.complianceRiskSubmission.create(
      {
        data: {
          risk: {
            connect: { id: submissionData.riskId },
          },
          evidence: {
            connect: { id: submissionData.evidenceId },
          },
          metrics: {
            connect: submissionData.metricIds.map((id) => ({ id })),
          },
          status: ComplianceRiskSubmissionStatus.SUBMITTED,
          submittedBy: uploadedBy,
          comment: submissionData.comment,
        },
      },
    );

    this.temporalProvider.runAsync(
      "complianceRiskSubmissionWorkflow",
      [submission.id],
      process.env.DEFAULT_QUEUE_NAME,
      `workflow-complianceRiskSubmissionWorkflow-${getUuidSlug()}`,
    );

    await this.updateComplianceRisk(submissionData.riskId, {
      status: ComplianceRiskStatus.INPROGRESS,
    });
    return new ComplianceRiskSubmission(submission);
  }

  /**
   * Find a compliance risk submission by id
   * @param {number} id submission id
   * @returns {Promise<ComplianceRiskSubmission>} compliance risk submission
   */
  async findRiskSubmission(id: number): Promise<ComplianceRiskSubmission> {
    const submission =
      await this.prismaService.complianceRiskSubmission.findUnique({
        where: { id },
        include: {
          metrics: true,
          risk: {
            include: {
              project: true,
              rule: true,
            },
          },
          evidence: true,
        },
      });

    return new ComplianceRiskSubmission({
      ...submission,
      project: submission.risk.project,
    });
  }

  /**
   * Attach a request to a compliance risk submission
   * @param {number} id submission id
   * @param {number} requestId request id
   * @returns {Promise<ComplianceRiskSubmission>} updated submission
   */
  async attachRequestToRiskSubmission(id: number, requestId: number) {
    return this.prismaService.complianceRiskSubmission.update({
      where: { id },
      data: {
        request: {
          connect: { id: requestId },
        },
      },
    });
  }

  /**
   * Update a compliance risk submission status
   * @param {number} id submission id
   * @param {ComplianceRiskSubmissionStatus} status status
   * @param {string} evaluatedBy user id
   * @returns {Promise<ComplianceRiskSubmission>} updated submission
   */
  async updateRiskSubmission(
    id: number,
    status: ComplianceRiskSubmissionStatus,
    evaluatedBy?: string,
  ) {
    const submission = await this.prismaService.complianceRiskSubmission.update(
      {
        where: { id },
        data: { status, evaluatedBy: evaluatedBy ? evaluatedBy : null },
      },
    );
    return new ComplianceRiskSubmission(submission);
  }

  /**
   * Function to get dependent projects for a list of rule ids
   * @param ruleIds
   * @returns Promise<number[]> list of project ids
   */
  getDependentProjects(ruleIds: number[]): Promise<number[]> {
    return this.prismaService.complianceRisk
      .findMany({
        distinct: ["projectId"],
        where: {
          ruleId: {
            in: ruleIds,
          },
          project: {
            stage: {
              not: ProjectStages.DELETED,
            },
          },
        },
      })
      .then((risk) => risk.map((risk) => risk.projectId));
  }

  /**
   * Function to delete rule dependent risks
   * deletes risks and declines requests for the risks that are submitted
   * @param ruleIds
   * @returns Promise<any>
   */
  async deleteRuleDependentRisk(ruleIds: number[]): Promise<any> {
    const riskToDelete = await this.prismaService.complianceRisk.findMany({
      where: {
        ruleId: {
          in: ruleIds,
        },
      },
      include: {
        submissions: true,
      },
    });

    return this.deleteDependentRisk(riskToDelete);
  }

  /**
   * Function to delete policy dependent risks
   * deletes risks and declines requests for the risks that are submitted
   * @param policyId
   * @returns
   */
  async deletePolicyDependentRisk(policyId: number) {
    const dependentRisks = await this.prismaService.complianceRisk.findMany({
      where: {
        policies: {
          some: {
            id: policyId,
          },
        },
      },
      include: {
        submissions: true,
        policies: true,
      },
    });

    const riskToDelete = [];

    dependentRisks.forEach((risk) => {
      if (risk.policies.length == 1) {
        riskToDelete.push(risk);
      }
    });

    return this.deleteDependentRisk(riskToDelete);
  }

  /**
   * Function to delete dependent risks
   * deletes risks and declines requests for the risks that are submitted
   * @param riskToDelete
   * @returns
   */
  async deleteDependentRisk(riskToDelete: ComplianceRisk[]): Promise<any> {
    await Promise.all(
      riskToDelete.map((risk) => {
        Promise.all(
          risk.submissions.map(async (submission) => {
            if (
              submission["status"] === ComplianceRiskSubmissionStatus.SUBMITTED
            ) {
              await this.requestService.declineRequest(
                submission["requestId"],
                "system@turo.com",
              );
            }
          }),
        );
        return this.deleteComplianceRisk(risk.id);
      }),
    );

    return riskToDelete;
  }
}

results matching ""

    No results matching ""