@@ -7,10 +7,14 @@ import type {
77} from '@/lib/schemas/assessment.schema' ;
88import { AssessmentStatus } from '@/generated/prisma' ;
99import { BadRequestException , NotFoundException } from '@/lib/utils/errors.utils' ;
10- import { type AssessmentWithRelations } from '@/lib/types/assessment-template.types' ;
10+ import {
11+ type AssessmentWithRelations ,
12+ type AssessmentInvitationResult ,
13+ } from '@/lib/types/assessment-template.types' ;
1114import type { CandidateAssessment } from '@/lib/types/candidate-assessment.types' ;
1215import type { BlockNoteContent } from '@/lib/types/task-template.types' ;
1316import type { TestCaseDTO } from '@/lib/schemas/task-template.schema' ;
17+ import emailService from '@/lib/services/email.service' ;
1418
1519async function getAssessmentWithRelations (
1620 id : string ,
@@ -338,6 +342,73 @@ async function submitAssessmentForCandidate(assessmentId: string): Promise<void>
338342 ] ) ;
339343}
340344
345+ async function sendAssessmentInvitationsToPosition (
346+ positionId : string ,
347+ orgId : string
348+ ) : Promise < AssessmentInvitationResult > {
349+ const position = await prisma . position . findUnique ( {
350+ where : { id : positionId } ,
351+ select : { assessmentId : true , orgId : true } ,
352+ } ) ;
353+
354+ if ( ! position ) {
355+ throw new NotFoundException ( 'Position' , positionId ) ;
356+ }
357+
358+ if ( position . orgId !== orgId ) {
359+ throw new BadRequestException ( 'Position does not belong to your organization' ) ;
360+ }
361+
362+ if ( ! position . assessmentId ) {
363+ throw new BadRequestException ( 'Position does not have an assessment template assigned' ) ;
364+ }
365+
366+ const applications = await prisma . application . findMany ( {
367+ where : {
368+ positionId,
369+ assessmentStatus : 'NOT_SENT' ,
370+ } ,
371+ include : {
372+ candidate : true ,
373+ } ,
374+ } ) ;
375+
376+ const results = [ ] ;
377+ for ( const application of applications ) {
378+ try {
379+ const result = await emailService . sendAssessmentInvitationEmail (
380+ application . candidate . id ,
381+ orgId
382+ ) ;
383+
384+ await prisma . application . update ( {
385+ where : { id : application . id } ,
386+ data : { assessmentStatus : 'NOT_STARTED' } ,
387+ } ) ;
388+
389+ results . push ( {
390+ ...result ,
391+ applicationId : application . id ,
392+ } ) ;
393+ } catch ( err ) {
394+ results . push ( {
395+ success : false ,
396+ message : `Failed to send invitation to ${ application . candidate . name } : ${ ( err as Error ) . message } ` ,
397+ candidateName : application . candidate . name ,
398+ positionTitle : '' ,
399+ assessmentId : '' ,
400+ applicationId : application . id ,
401+ } ) ;
402+ }
403+ }
404+
405+ return {
406+ totalSent : results . filter ( ( r ) => r . success ) . length ,
407+ totalFailed : results . filter ( ( r ) => ! r . success ) . length ,
408+ results,
409+ } ;
410+ }
411+
341412const AssessmentService = {
342413 getAssessmentWithRelations,
343414 getAssessmentForCandidate,
@@ -346,6 +417,7 @@ const AssessmentService = {
346417 assignTemplateToPosition,
347418 deleteAssessment,
348419 updateAssessment,
420+ sendAssessmentInvitationsToPosition,
349421} ;
350422
351423export default AssessmentService ;
0 commit comments