import { PaginatedResponse } from '@edr/types'; import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; import { CreateApprovalRuleDto } from '../dto/create-approval-rule.dto'; import { ListApprovalRulesQueryDto } from '../dto/list-rule-engine-query.dto'; import { ReorderItemsDto } from '../dto/reorder-items.dto'; import { UpdateApprovalRuleDto } from '../dto/update-approval-rule.dto'; import { ApprovalRule } from '../entities/approval-rule.entity'; import { APPROVAL_RULES_REPOSITORY, IApprovalRulesRepository, } from '../interfaces/approval-rules.repository.interface'; import { DisplayOrderService } from './display-order.service'; @Injectable() export class ApprovalRulesService { constructor( @Inject(APPROVAL_RULES_REPOSITORY) private readonly repository: IApprovalRulesRepository, private readonly displayOrder: DisplayOrderService, ) {} /** List approval rules — standard paginated envelope with server-side search. */ async findAll(query: ListApprovalRulesQueryDto): Promise> { return this.repository.findPaged(query); } /** Get approval chain for a cargo type flag. */ async findChain(requiresDirectorApproval: boolean): Promise { return this.repository.findChainForCargo(requiresDirectorApproval); } /** Get an approval rule by ID. */ async findById(id: string): Promise { const entity = await this.repository.findById(id); if (!entity) throw new NotFoundException(`Approval rule ${id} not found`); return entity; } /** Create an approval rule step. */ async create(dto: CreateApprovalRuleDto): Promise { if (dto.stepOrder !== undefined && dto.insertAfterId) { throw new BadRequestException('Cannot set both stepOrder and insertAfterId'); } const scopeWhere = { requiresDirectorApproval: dto.requiresDirectorApproval }; const stepOrder = await this.displayOrder.resolveCreateOrder(ApprovalRule, 'stepOrder', { explicitOrder: dto.stepOrder, insertAfterId: dto.insertAfterId, scopeWhere, }); return this.repository.create({ requiresDirectorApproval: dto.requiresDirectorApproval, stepOrder, requiredRole: dto.requiredRole, actionLabel: dto.actionLabel, blocksRole: dto.blocksRole, }); } /** Update an approval rule. */ async update(id: string, dto: UpdateApprovalRuleDto): Promise { await this.findById(id); const updated = await this.repository.update(id, dto); if (!updated) throw new NotFoundException(`Approval rule ${id} not found`); return updated; } /** Soft-delete an approval rule. */ async remove(id: string): Promise { await this.findById(id); await this.repository.softDelete(id); } async reorder(dto: ReorderItemsDto): Promise { if (dto.requiresDirectorApproval === undefined) { throw new BadRequestException('requiresDirectorApproval is required for approval rule reorder'); } await this.displayOrder.reorderByIds(ApprovalRule, 'stepOrder', dto.ids, { requiresDirectorApproval: dto.requiresDirectorApproval, }); } async moveOrder(id: string, direction: 'up' | 'down'): Promise { const rule = await this.findById(id); await this.displayOrder.moveOne(ApprovalRule, 'stepOrder', id, direction, { requiresDirectorApproval: rule.requiresDirectorApproval, }); } }