Guida processo

Candidati pre-onboarding (HR)

Pipeline candidati prima dell'assunzione: screening, colloqui, offerta, conversione in utente del tenant.

Candidati pre-onboarding (HR)

Ultimo aggiornamento: 2026-05-25 Owner: Product Writer Audience: hr_admin, admin tenant, owner

Scopo

Spiegare come HR puo' gestire la pipeline di candidati prima dell'assunzione: sourcing, screening, colloqui, offerta, fino all'eventuale conversione in utente del tenant. I candidati sono profili dedicati, separati da auth.users (un candidato non e' ancora un membro dell'organizzazione).

Concetti chiave

  • Candidato vs dipendente: il candidato e' un *prospect* — un profilo lavorativo che stai valutando. Non ha login, non vede dati del tenant, non occupa un posto utente della suite. Diventa dipendente solo quando viene convertito (status hired + link a un utente del tenant).
  • Scope HR only: tutta la sezione Candidati pre-onboarding e' riservata a owner, admin tenant, hr_admin. I manager non hanno accesso (403 con reason candidates_require_hr_admin).
  • Workflow lineare: Nuovo → Screening → Colloquio → Offerta → Assunto, con possibili uscite Rifiutato, Ritirato, Archiviato.
  • CV dedicati: i CV dei candidati vivono in un bucket dedicato (hr-candidate-cvs), separato da quello dei CV dipendenti (hr-cvs). Quando carichi una nuova versione, la precedente resta archiviata ma non e' piu' la corrente.
  • Archive vs Elimina: due azioni distinte. Archive e' uno stato di workflow (Archiviato, recuperabile cambiando lo status). Elimina sposta nel cestino (deleted_at, recuperabile entro 30 giorni dalla vista cestino).
  • Owner e tag: ogni candidato puo' avere un *owner HR* assegnato (chi lo segue) e una lista di *tag* (es. ruolo, fonte, focus) — utili per triage e filtri.

Prerequisiti

  • ruolo: owner, admin, hr_admin
  • migration 0077_hr_candidates.sql + 0080_hr_candidates_soft_delete.sql applicate sul progetto Supabase del tenant
  • bucket hr-candidate-cvs creato (creato dalla migration 0077)

Accesso rapido

  • Pagina pipeline: Workspace › HR › Gestione profili › 🆕 Candidati pre-onboarding
  • URL diretto: /workspace/hr/candidates

Passi operativi

1. Crea un candidato

  1. Apri /workspace/hr/candidates
  2. Click sulla CTA + Nuovo candidato in alto a destra
  3. Compila il modal: solo Nome e cognome e' obbligatorio. Email, telefono, headline, sorgente e note sono opzionali — li puoi completare durante lo screening.
  4. Click Crea candidato. Lo stato iniziale e' Nuovo.

2. Aggiorna il profilo del candidato

  1. Le modifiche vengono salvate automaticamente quando esci dal campo (blur)

3. Estrai i dati dal CV candidato

L'estrazione AI del CV e' on-demand: carichi il PDF/DOCX, poi clicchi un bottone per popolare il draft strutturato (esperienze, skill, lingue, anagrafica). E' un prerequisito per il trasferimento profilo durante la conversione (step 4).

  1. Dal dettaglio candidato, sezione CV candidato, identifica la riga del CV caricato (colonna Stato mostra Caricato)
  2. Click su Estrai dati CV (visibile finche' lo stato non e' Dati estratti)
  3. Il sistema scarica il file, lo parsa (incluso OCR fallback per PDF scansionati) e chiama Claude Haiku per estrarre il profilo strutturato. Lo stato passa a Dati estratti.
  4. Fail-soft: se il parse o la chiamata AI falliscono, il CV resta Caricato e l'estrazione e' best-effort — la conversione funziona comunque, ma trasferisce 0 esperienze/skill.

4. Converti il candidato in dipendente

Quando il candidato accetta l'offerta, lo converti in dipendente del tenant. La conversione e' riservata a owner, admin, hr_admin.

  1. Alla conferma il sistema esegue la conversione, marca il candidato come Assunto e mostra l'esito con il link al profilo dipendente.

Due modalita' di conversione:

  • Crea nuovo utente (new_user): crea un nuovo account auth ex-novo, ne imposta profiles, organization_memberships (con il ruolo scelto) e organization_user_details (anagrafica derivata dal candidato), e invia un'email di invito con link per impostare la password. Richiede un'email non gia' presente tra gli utenti.
  • Collega a utente esistente (linked_existing): linka il candidato a un membro gia' presente nel tenant. Nessun nuovo account, nessuna email — solo aggiornamento profilo + trasferimento risorse.

Cosa viene trasferito (richiede CV con Dati estratti — vedi step 3):

  • CV: gli oggetti storage vengono copiati dal bucket candidati (hr-candidate-cvs) al bucket dipendenti (hr-cvs), con una nuova riga in hr_cv_files (versione 1). Il CV candidato viene marcato migrated.
  • Esperienze lavorative: le esperienze dell'estrazione CV vengono inserite sul profilo del dipendente.
  • Skill: il mapping verso il catalogo del tenant avviene gia' all'estrazione (entity-linking, link salvati nel draft); la conversione applica al profilo solo i link auto (confidenza >= 0.85), i match fuzzy 0.70-0.85 restano suggerimenti non applicati (CV_ENTITY_GRAPH_01 W01).

Se l'estrazione CV non e' disponibile, la conversione procede comunque ma non trasferisce esperienze/skill (contatori a 0).

Email di invito (solo modalita' new_user): l'invio e' *fail-soft* — se Resend non e' raggiungibile la conversione resta valida; l'esito segnala l'invito non inviato e puoi inviare manualmente un reset password.

Sicurezza transazionale: gli oggetti storage vengono copiati prima delle scritture DB. Se una scrittura DB fallisce dopo la creazione dell'utente, un rollback compensante elimina l'utente auth creato e gli oggetti copiati. Ogni conversione (riuscita o annullata) lascia una riga di audit in hr_candidate_conversions.

Limiti posti: se il piano del tenant ha un limite max_users gia' saturo, la modalita' new_user fallisce con un errore esplicito (422) prima di creare l'utente.

Da quel momento il candidato resta tracciato come record storico (audit GDPR), ma il *vero* profilo professionale e' quello del dipendente.

5. Archivia un candidato

  • Dal dettaglio: bottone Archivia (o cambio status manuale a Archiviato)
  • Il candidato sparisce dalla vista di default; per rivederlo seleziona Archiviato dal filtro stato.
  • L'archive e' uno stato di workflow — il record resta visibile cambiando il filtro. Per la rimozione recuperabile usa Elimina (step 6).

6. Elimina nel cestino (e ripristina)

  • Dal dettaglio: bottone Elimina (ghost, accanto ad Archivia). Conferma: recuperabile entro 30 giorni.
  • Il candidato e' soft-deleted (deleted_at) e sparisce da tutte le viste di default — non e' filtrabile per status.
  • Per recuperarlo: nella lista, spunta Mostra eliminati (cestino, 30gg) per vedere la vista cestino; click Ripristina sulla riga del candidato.
  • Hard delete automatico fuori scope: i candidati nel cestino restano finche' un operatore (o un futuro cron) non li elimina definitivamente.

Filtri lista candidati

La lista /workspace/hr/candidates espone 4 filtri primari (auto-applicati su change, reset via Pulisci filtri):

  • Cerca per nome, email o headline: ILIKE su full_name, email, headline
  • Stato pipeline: dropdown con tutti gli status (default: esclude Archiviato)
  • Owner assegnato: dropdown popolato dagli utenti tenant con permessi HR; filtra per assigned_to_user_id
  • Tag: dropdown single-select derivato dall'unione delle tag dei candidati attualmente caricati; filtra per tags && [tag] (PG overlap)

Toggle separato Mostra eliminati (cestino, 30gg) per accedere alla vista soft-delete.

Notifiche email transazionali

Su cambio di status, il sistema invia automaticamente un'email al candidato (se email non null) per 4 transizioni:

  • Screening — "candidatura presa in carico"
  • Colloquio — "invito a colloquio"
  • Offerta — "offerta estesa"
  • Rifiutato — "esito candidatura"

Gli altri status (Nuovo/Assunto/Archiviato/Ritirato) non triggera notifiche (l'invito per Assunto e' gestito separatamente dalla conversione, step 4).

Fail-soft: la chiamata Resend e' fire-and-forget; un errore SMTP non blocca la transizione di stato. Errori vengono loggati lato server.

Credenziali: usano platform_email_settings (provider=resend) se configurato sul tenant, altrimenti RESEND_API_KEY da env. Senza nessuna delle due → skip silenzioso.

Sorgenti supportate

Il campo Sorgente e' libero ma il modal di creazione propone preset comuni:

  • linkedin — sourcing diretto da LinkedIn
  • referral — segnalazione interna
  • gara — candidato emerso durante una RFP / gara
  • cv-upload — upload CV diretto (es. tramite form pubblico, in roadmap)
  • website — applicazione spontanea da sito
  • agency — agenzia esterna
  • other — altro

API endpoint (per integrazioni)

Tutti gli endpoint richiedono header Authorization: Bearer <jwt> + x-horis-org-id: <org_uuid>.

| Metodo | URL | Descrizione | |--------|-----|-------------| | GET | /api/hr/candidates | lista paginata + filtri (status, search, assignedTo, tags, includeArchived, includeDeleted, onlyDeleted, page, limit). tags accetta csv. | | POST | /api/hr/candidates | crea (body JSON: fullName obbligatorio + opzionali, incluso tags/assignedToUserId) | | GET | /api/hr/candidates/{id} | dettaglio + cvFiles | | PATCH | /api/hr/candidates/{id} | update parziale (incluso tags/assignedToUserId/status — su status triggerabile + email non null → invia email transazionale) | | DELETE | /api/hr/candidates/{id} | archive (status → archived, NON soft delete — vedi /soft-delete) | | POST | /api/hr/candidates/{id}/soft-delete | sposta nel cestino (deleted_at), recuperabile entro 30 giorni | | POST | /api/hr/candidates/{id}/restore | ripristina dal cestino | | GET | /api/hr/candidates/{id}/cv-files | lista CV candidato | | POST | /api/hr/candidates/{id}/cv-files | upload multipart (file) | | POST | /api/hr/candidates/{id}/cv-files/{cvId}/extract | estrazione AI on-demand del CV → popola extraction_draft con il profilo (esperienze/skill/lingue/anagrafica). Fail-soft: 200 con warning se parse/LLM falliscono. | | GET | /api/hr/candidates/{id}/cv-files/{cvId}/signed-url | signed URL TTL 300s (max 3600s via ?ttl=) | | DELETE | /api/hr/candidates/{id}/cv-files/{cvId} | soft delete CV | | POST | /api/hr/candidates/{id}/convert-to-user | converti candidato → dipendente. Body: mode (new_user\|linked_existing), + targetEmail/existingUserId, role, managerUserId, primaryRole, startDate, transferCv/transferExperiences/transferSkills. Backward-compat: solo existingUserIdlinked_existing. Errori: 409 email_collision, 422 seat_limit_exceeded, 409 already_hired |

Stati pipeline

| Status | Significato | Transizioni valide | |--------|-------------|--------------------| | new | Appena creato, non ancora screenato | qualsiasi | | screening | In valutazione iniziale | qualsiasi | | interview | In fase colloqui | qualsiasi | | offered | Offerta inviata | hired, rejected, withdrawn | | hired | Assunto (post-convert) | solo verso archived, rejected, withdrawn (no ritorno indietro) | | rejected | Scartato dopo valutazione | terminale (puoi sempre archiviare) | | withdrawn | Si e' ritirato lui | terminale | | archived | Stato workflow, NON eliminazione | riattivabile spostando lo status |

Sicurezza & GDPR

  • Tabelle hr_candidates e hr_candidate_cv_files hanno RLS bloccata totalmente: l'accesso e' SOLO via service-role admin client server-side, con check applicativo canManageCandidates.
  • I CV vivono in bucket privato hr-candidate-cvs, con signed URL temporanei (TTL default 5 minuti).
  • Audit fields: created_by_user_id, uploaded_by_user_id, deleted_by_user_id su CV files. Su hr_candidates c'e' created_by_user_id + updated_at autotouch via trigger + deleted_at/deleted_by_user_id per soft delete.
  • Soft delete (sia per candidati nel cestino che per CV): il record resta per audit, lo storage object resta nel bucket fino a hard delete esplicito (futuro).
  • Email transazionali: fail-soft, mai bloccanti. Gli errori SMTP non lasciano residui silenziosi (loggati lato server).

Cosa NON c'e' (ancora)

  • assegnazione interviewer + scheduling colloqui
  • public form per candidatura spontanea
  • job postings / pipeline ATS
  • background check
  • trasferimento candidato cross-org
  • hard delete automatico dal cestino (cron)
  • email template multi-lingua e tenant-configurable

Programma di provenienza

  • HORIS_HR_PRE_ONBOARDING_01 (2026-05-19) — versione iniziale
  • HORIS_HR_PRE_ONBOARDING_02 (2026-05-19) — full hire workflow: conversione candidato → dipendente completa (provisioning nuovo utente, trasferimento profilo/esperienze/skill/CV, invito email)
  • HORIS_HR_CANDIDATE_CONSOLIDATION_01 (2026-05-25) — consolidamento: W01 AI extractor on-demand sui CV candidati (chiude il gap profilo-non-trasferito), W02 UI tags filter + owner picker (lista + dettaglio), W03 email transazionali + soft delete con grace period 30gg

Guide correlate