@@ -3509,6 +3509,105 @@ Return ONLY valid JSON in format: {"match_score": 85, "reason": "Brief 1-sentenc
35093509 auto_rejected : autoRejected ,
35103510 match_score,
35113511 } ) ;
3512+
3513+ // ═══════════════════════════════════════════════════════════════
3514+ // 7. SERVER-SIDE AUTOMATION: Evaluate 'app_received' rules
3515+ // This ensures automations fire even when no user has the
3516+ // ATS dashboard open (e.g., candidates applying via careers page)
3517+ // ═══════════════════════════════════════════════════════════════
3518+ try {
3519+ const rulesRes = await sbFetch (
3520+ env ,
3521+ "GET" ,
3522+ `/rest/v1/automation_rules?enabled=eq.true&trigger=eq.app_received&module=eq.ats&select=*` ,
3523+ null ,
3524+ false ,
3525+ ctx . tenantId ,
3526+ ) ;
3527+ const rules = await rulesRes . json ( ) ;
3528+
3529+ if ( Array . isArray ( rules ) && rules . length > 0 ) {
3530+ console . log ( `[ATS-AUTO] Evaluating ${ rules . length } app_received rule(s) for tenant ${ ctx . tenantId } ` ) ;
3531+
3532+ for ( const rule of rules ) {
3533+ try {
3534+ // Check conditions
3535+ if ( rule . cond_score && match_score !== null && match_score < parseInt ( rule . cond_score ) ) {
3536+ continue ; // Score below threshold, skip
3537+ }
3538+ if ( rule . cond_stage && status !== rule . cond_stage ) {
3539+ continue ; // Stage mismatch, skip
3540+ }
3541+
3542+ const actions = rule . actions || [ ] ;
3543+ const results = [ ] ;
3544+
3545+ for ( const action of actions ) {
3546+ switch ( action . type ) {
3547+ case 'send_email' :
3548+ // Email is already handled by the auto-shortlist/reject/confirmation flow above
3549+ results . push ( { type : 'send_email' , status : 'skipped' , detail : 'Handled by built-in email flow' } ) ;
3550+ break ;
3551+
3552+ case 'move_stage' :
3553+ if ( action . target && action . target !== status ) {
3554+ await sbFetch ( env , "PATCH" , `/rest/v1/job_applications?id=eq.${ app . id } ` , { status : action . target } , false , ctx . tenantId ) ;
3555+ results . push ( { type : 'move_stage' , status : 'success' , detail : `Moved to ${ action . target } ` } ) ;
3556+ } else {
3557+ results . push ( { type : 'move_stage' , status : 'skipped' , detail : 'Already in target stage' } ) ;
3558+ }
3559+ break ;
3560+
3561+ case 'shortlist' :
3562+ if ( status !== 'screening' ) {
3563+ await sbFetch ( env , "PATCH" , `/rest/v1/job_applications?id=eq.${ app . id } ` , { status : 'screening' } , false , ctx . tenantId ) ;
3564+ results . push ( { type : 'shortlist' , status : 'success' , detail : 'Moved to screening' } ) ;
3565+ }
3566+ break ;
3567+
3568+ case 'reject_candidate' :
3569+ if ( status !== 'rejected' ) {
3570+ await sbFetch ( env , "PATCH" , `/rest/v1/job_applications?id=eq.${ app . id } ` , { status : 'rejected' } , false , ctx . tenantId ) ;
3571+ results . push ( { type : 'reject_candidate' , status : 'success' , detail : 'Auto-rejected by rule' } ) ;
3572+ }
3573+ break ;
3574+
3575+ default :
3576+ results . push ( { type : action . type , status : 'dispatched' , detail : action . msg || action . type } ) ;
3577+ }
3578+ }
3579+
3580+ // Log execution
3581+ const logEntry = {
3582+ rule_name : rule . name ,
3583+ trigger : 'app_received' ,
3584+ module : 'ats' ,
3585+ status : 'success' ,
3586+ detail : `[SERVER] ${ results . map ( r => r . type + ':' + r . status ) . join ( ', ' ) } ` ,
3587+ target_id : app . id ,
3588+ action_taken : results . map ( r => r . detail ) . join ( ' | ' ) ,
3589+ created_at : new Date ( ) . toISOString ( ) ,
3590+ ts : new Date ( ) . toISOString ( ) ,
3591+ tenant_id : ctx . tenantId ,
3592+ } ;
3593+ await sbFetch ( env , "POST" , "/rest/v1/automation_logs" , logEntry , false , ctx . tenantId ) ;
3594+
3595+ // Update run count
3596+ await sbFetch ( env , "PATCH" , `/rest/v1/automation_rules?id=eq.${ rule . id } ` , {
3597+ run_count : ( rule . run_count || 0 ) + 1 ,
3598+ last_run_at : new Date ( ) . toISOString ( ) ,
3599+ } , false , ctx . tenantId ) ;
3600+
3601+ console . log ( `[ATS-AUTO] Rule "${ rule . name } " executed for application ${ app . id } ` ) ;
3602+ } catch ( ruleErr ) {
3603+ console . warn ( `[ATS-AUTO] Rule "${ rule . name } " failed:` , ruleErr . message ) ;
3604+ }
3605+ }
3606+ }
3607+ } catch ( autoErr ) {
3608+ console . warn ( "[ATS-AUTO] Server-side automation evaluation failed:" , autoErr . message ) ;
3609+ }
3610+
35123611 return apiResponse (
35133612 { application : app , auto_scheduled : autoInterview , auto_rejected : autoRejected , match_score } ,
35143613 HTTP . CREATED ,
0 commit comments