This commit is contained in:
Michael Abebe
2026-06-02 13:43:44 +03:00
parent 2a489ca18d
commit 88a280eea2
8 changed files with 851 additions and 49 deletions

View File

@@ -5,6 +5,7 @@ import {
Param,
ParseUUIDPipe,
Post,
Query,
Put,
} from "@nestjs/common";
import { ApiOperation, ApiTags } from "@nestjs/swagger";
@@ -27,6 +28,19 @@ export class BackofficeController {
return this.backofficeService.createOrganizationUser(organizationId, dto);
}
@Get("organizations/:orgId/employees")
@ApiOperation({ summary: "Get deduplicated organization employees for backoffice" })
getOrganizationEmployees(
@Param("orgId", ParseUUIDPipe) organizationId: string,
@Query("skip") skip?: string,
@Query("take") take?: string,
) {
return this.backofficeService.getOrganizationEmployees(organizationId, {
skip,
take,
});
}
@Get("organizations/:orgId/employee-users/:userId/roles")
@ApiOperation({ summary: "Get org-scoped roles assigned to an employee user" })
getEmployeeUserRoles(

View File

@@ -27,6 +27,8 @@ const EDR_ORG_MANAGER_ROLE_KEY = "edr_org_manager";
@Injectable()
export class BackofficeService {
constructor(
@InjectRepository(Employee)
private readonly employeeRepository: Repository<Employee>,
@InjectRepository(Organization)
private readonly organizationRepository: Repository<Organization>,
@InjectRepository(Role)
@@ -214,6 +216,45 @@ export class BackofficeService {
}));
}
async getOrganizationEmployees(
organizationId: string,
query: { skip?: string; take?: string },
) {
const organizationExists = await this.organizationRepository.exists({
where: { id: organizationId },
});
if (!organizationExists) {
throw new NotFoundException("organization_not_found");
}
const take = Number.parseInt(query.take ?? "1000", 10);
const skip = Number.parseInt(query.skip ?? "0", 10);
const employees = await this.employeeRepository.find({
where: {
organizationId,
isCurrent: true,
},
relations: {
user: true,
employeePositions: {
position: true,
},
},
order: {
createdAt: "DESC",
},
});
const deduplicated = this.mergeEmployeesByUser(employees);
return {
count: deduplicated.length,
items: deduplicated.slice(skip, skip + take),
};
}
async replaceEmployeeUserRoles(
organizationId: string,
userId: string,
@@ -282,6 +323,53 @@ export class BackofficeService {
}
}
private mergeEmployeesByUser(employees: Employee[]) {
const employeesByUserId = new Map<string, Employee>();
for (const employee of employees) {
const userId = employee.userId;
const employeeId = employee.id;
if (!userId) {
if (employeeId) {
employeesByUserId.set(employeeId, employee);
}
continue;
}
const existing = employeesByUserId.get(userId);
if (!existing) {
employeesByUserId.set(userId, employee);
continue;
}
const existingPositions = existing.employeePositions ?? [];
const nextPositions = employee.employeePositions ?? [];
const mergedEmployeePositions = Array.from(
new Map(
[...existingPositions, ...nextPositions].map((employeePosition) => [
employeePosition.id,
employeePosition,
]),
).values(),
);
employeesByUserId.set(userId, {
...existing,
...employee,
id: existing.id,
user: existing.user ?? employee.user,
userId,
name: existing.name ?? employee.name,
status: existing.status ?? employee.status,
employeePositions: mergedEmployeePositions,
});
}
return [...employeesByUserId.values()];
}
private async ensureOrganizationAdminAccess(
manager: EntityManager,
organizationId: string,