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')
}