Bluewoo HRMS
AI Development GuideDevelopment

Service Implementation

Reference implementation for OrgService and helper functions

Service Implementation

This document provides the reference implementation for the OrgService, which handles all organizational structure operations.

OrgService

// apps/api/src/modules/org/org.service.ts

import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common'
import { PrismaService } from '@/prisma/prisma.service'

@Injectable()
export class OrgService {
  constructor(private prisma: PrismaService) {}

  // ============================================
  // MANAGER OPERATIONS
  // ============================================

  async assignPrimaryManager(
    tenantId: string,
    employeeId: string,
    managerId: string
  ): Promise<void> {
    await this.validateManagerAssignment(tenantId, employeeId, managerId)

    await this.prisma.employeeOrgRelations.upsert({
      where: { employeeId },
      create: { employeeId, primaryManagerId: managerId },
      update: { primaryManagerId: managerId },
    })
  }

  async removePrimaryManager(employeeId: string): Promise<void> {
    await this.prisma.employeeOrgRelations.update({
      where: { employeeId },
      data: { primaryManagerId: null },
    })
  }

  async addDottedLineManager(
    tenantId: string,
    employeeId: string,
    managerId: string
  ): Promise<void> {
    await this.validateManagerAssignment(tenantId, employeeId, managerId)
    const orgRelations = await this.getOrCreateOrgRelations(employeeId)

    await this.prisma.employeeDottedLine.create({
      data: { orgRelationsId: orgRelations.id, managerId },
    })
  }

  async removeDottedLineManager(
    employeeId: string,
    managerId: string
  ): Promise<void> {
    const orgRelations = await this.prisma.employeeOrgRelations.findUnique({
      where: { employeeId },
    })
    if (!orgRelations) return

    await this.prisma.employeeDottedLine.deleteMany({
      where: { orgRelationsId: orgRelations.id, managerId },
    })
  }

  async addAdditionalManager(
    tenantId: string,
    employeeId: string,
    managerId: string
  ): Promise<void> {
    await this.validateManagerAssignment(tenantId, employeeId, managerId)
    const orgRelations = await this.getOrCreateOrgRelations(employeeId)

    await this.prisma.employeeAdditionalManager.create({
      data: { orgRelationsId: orgRelations.id, managerId },
    })
  }

  async removeAdditionalManager(
    employeeId: string,
    managerId: string
  ): Promise<void> {
    const orgRelations = await this.prisma.employeeOrgRelations.findUnique({
      where: { employeeId },
    })
    if (!orgRelations) return

    await this.prisma.employeeAdditionalManager.deleteMany({
      where: { orgRelationsId: orgRelations.id, managerId },
    })
  }

  // ============================================
  // TEAM OPERATIONS
  // ============================================

  async addEmployeeToTeam(
    employeeId: string,
    teamId: string,
    role?: string
  ): Promise<void> {
    const orgRelations = await this.getOrCreateOrgRelations(employeeId)

    await this.prisma.employeeTeam.create({
      data: { orgRelationsId: orgRelations.id, teamId, role },
    })
  }

  async removeEmployeeFromTeam(
    employeeId: string,
    teamId: string
  ): Promise<void> {
    const orgRelations = await this.prisma.employeeOrgRelations.findUnique({
      where: { employeeId },
    })
    if (!orgRelations) return

    await this.prisma.employeeTeam.deleteMany({
      where: { orgRelationsId: orgRelations.id, teamId },
    })
  }

  async updateTeamRole(
    employeeId: string,
    teamId: string,
    role: string
  ): Promise<void> {
    const orgRelations = await this.prisma.employeeOrgRelations.findUnique({
      where: { employeeId },
    })
    if (!orgRelations) {
      throw new NotFoundException('Employee org relations not found')
    }

    await this.prisma.employeeTeam.updateMany({
      where: { orgRelationsId: orgRelations.id, teamId },
      data: { role },
    })
  }

  // ============================================
  // DEPARTMENT OPERATIONS
  // ============================================

  async addEmployeeToDepartment(
    employeeId: string,
    departmentId: string,
    isPrimary: boolean = false
  ): Promise<void> {
    const orgRelations = await this.getOrCreateOrgRelations(employeeId)

    if (isPrimary) {
      await this.prisma.employeeDepartment.updateMany({
        where: { orgRelationsId: orgRelations.id, isPrimary: true },
        data: { isPrimary: false },
      })
    }

    await this.prisma.employeeDepartment.create({
      data: { orgRelationsId: orgRelations.id, departmentId, isPrimary },
    })
  }

  async removeEmployeeFromDepartment(
    employeeId: string,
    departmentId: string
  ): Promise<void> {
    const orgRelations = await this.prisma.employeeOrgRelations.findUnique({
      where: { employeeId },
    })
    if (!orgRelations) return

    await this.prisma.employeeDepartment.deleteMany({
      where: { orgRelationsId: orgRelations.id, departmentId },
    })
  }

  async setPrimaryDepartment(
    employeeId: string,
    departmentId: string
  ): Promise<void> {
    const orgRelations = await this.prisma.employeeOrgRelations.findUnique({
      where: { employeeId },
    })
    if (!orgRelations) {
      throw new NotFoundException('Employee org relations not found')
    }

    await this.prisma.$transaction([
      this.prisma.employeeDepartment.updateMany({
        where: { orgRelationsId: orgRelations.id, isPrimary: true },
        data: { isPrimary: false },
      }),
      this.prisma.employeeDepartment.updateMany({
        where: { orgRelationsId: orgRelations.id, departmentId },
        data: { isPrimary: true },
      }),
    ])
  }

  // ============================================
  // ROLE OPERATIONS
  // ============================================

  async assignRole(
    employeeId: string,
    roleId: string,
    isPrimary: boolean = false
  ): Promise<void> {
    const orgRelations = await this.getOrCreateOrgRelations(employeeId)

    if (isPrimary) {
      await this.prisma.employeeOrgRole.updateMany({
        where: { orgRelationsId: orgRelations.id, isPrimary: true },
        data: { isPrimary: false },
      })
    }

    await this.prisma.employeeOrgRole.create({
      data: { orgRelationsId: orgRelations.id, roleId, isPrimary },
    })
  }

  async removeRole(employeeId: string, roleId: string): Promise<void> {
    const orgRelations = await this.prisma.employeeOrgRelations.findUnique({
      where: { employeeId },
    })
    if (!orgRelations) return

    await this.prisma.employeeOrgRole.deleteMany({
      where: { orgRelationsId: orgRelations.id, roleId },
    })
  }

  async setPrimaryRole(employeeId: string, roleId: string): Promise<void> {
    const orgRelations = await this.prisma.employeeOrgRelations.findUnique({
      where: { employeeId },
    })
    if (!orgRelations) {
      throw new NotFoundException('Employee org relations not found')
    }

    await this.prisma.$transaction([
      this.prisma.employeeOrgRole.updateMany({
        where: { orgRelationsId: orgRelations.id, isPrimary: true },
        data: { isPrimary: false },
      }),
      this.prisma.employeeOrgRole.updateMany({
        where: { orgRelationsId: orgRelations.id, roleId },
        data: { isPrimary: true },
      }),
    ])
  }

  // ============================================
  // HELPERS
  // ============================================

  private async validateManagerAssignment(
    tenantId: string,
    employeeId: string,
    managerId: string
  ): Promise<void> {
    // Self-management check
    if (employeeId === managerId) {
      throw new BadRequestException('Employee cannot manage themselves')
    }

    // Manager must be active employee in same tenant
    const manager = await this.prisma.employee.findFirst({
      where: { id: managerId, tenantId, status: 'ACTIVE' },
    })

    if (!manager) {
      throw new NotFoundException('Manager not found or inactive')
    }
  }

  private async getOrCreateOrgRelations(employeeId: string) {
    return this.prisma.employeeOrgRelations.upsert({
      where: { employeeId },
      create: { employeeId },
      update: {},
    })
  }
}

Helper Functions

isManagerOf

Check if one employee manages another:

export async function isManagerOf(
  prisma: PrismaService,
  managerId: string,
  employeeId: string
): Promise<boolean> {
  const orgRelations = await prisma.employeeOrgRelations.findUnique({
    where: { employeeId },
    include: {
      dottedLineManagers: true,
      additionalManagers: true,
    },
  })

  if (!orgRelations) return false

  return (
    orgRelations.primaryManagerId === managerId ||
    orgRelations.dottedLineManagers.some(d => d.managerId === managerId) ||
    orgRelations.additionalManagers.some(a => a.managerId === managerId)
  )
}

getAllReports

Get all employees who report to a manager:

export async function getAllReports(
  prisma: PrismaService,
  managerId: string
): Promise<string[]> {
  const [primary, dotted, additional] = await Promise.all([
    prisma.employeeOrgRelations.findMany({
      where: { primaryManagerId: managerId },
      select: { employeeId: true },
    }),
    prisma.employeeDottedLine.findMany({
      where: { managerId },
      include: { orgRelations: true },
    }),
    prisma.employeeAdditionalManager.findMany({
      where: { managerId },
      include: { orgRelations: true },
    }),
  ])

  const reportIds = new Set<string>()
  primary.forEach(r => reportIds.add(r.employeeId))
  dotted.forEach(r => reportIds.add(r.orgRelations.employeeId))
  additional.forEach(r => reportIds.add(r.orgRelations.employeeId))

  return Array.from(reportIds)
}

getReportingChain

Get the chain of managers up to the top:

export async function getReportingChain(
  prisma: PrismaService,
  employeeId: string,
  maxDepth: number = 10
): Promise<string[]> {
  const chain: string[] = []
  let currentId = employeeId
  let depth = 0

  while (depth < maxDepth) {
    const orgRelations = await prisma.employeeOrgRelations.findUnique({
      where: { employeeId: currentId },
    })

    if (!orgRelations?.primaryManagerId) break

    chain.push(orgRelations.primaryManagerId)
    currentId = orgRelations.primaryManagerId
    depth++
  }

  return chain
}

Types

interface OrgGraphNode {
  employeeId: string
  firstName: string
  lastName: string
  jobTitle?: string
  pictureUrl?: string
  primaryManagerId?: string
  dottedLineManagerIds: string[]
  additionalManagerIds: string[]
  teamIds: string[]
  departmentIds: string[]
  roleIds: string[]
  directReports: OrgGraphNode[]
}

interface EmployeeSummary {
  id: string
  firstName: string
  lastName: string
  email: string
  jobTitle?: string
  pictureUrl?: string
}

interface EmployeeOrgSummary {
  employee: EmployeeSummary
  primaryManager: EmployeeSummary | null
  dottedLineManagers: EmployeeSummary[]
  additionalManagers: EmployeeSummary[]
  departments: { id: string; name: string; isPrimary: boolean }[]
  teams: { id: string; name: string; role?: string }[]
  roles: { id: string; name: string; category?: string; isPrimary: boolean }[]
  directReports: EmployeeSummary[]
}

Usage Examples

Assigning Multiple Managers

// CTO reports to CEO, with dotted line to CFO
await orgService.assignPrimaryManager(tenantId, ctoId, ceoId)
await orgService.addDottedLineManager(tenantId, ctoId, cfoId)

// Developer reports to Dev Lead, with additional manager CTO
await orgService.assignPrimaryManager(tenantId, devId, devLeadId)
await orgService.addAdditionalManager(tenantId, devId, ctoId)

Adding to Multiple Teams

// Add developer to SaaS team as Tech Lead
await orgService.addEmployeeToTeam(devId, saasTeamId, 'Tech Lead')

// Add same developer to Web team as Member
await orgService.addEmployeeToTeam(devId, webTeamId, 'Member')

Checking Permissions

// In a guard or service method
const canApprove = await isManagerOf(prisma, currentUserId, requestEmployeeId)

if (!canApprove) {
  throw new ForbiddenException('Only managers can approve requests')
}