Core HR & Onboarding (EMP)
Module code EMP · Release R1 (MVP); letters with e-sign and DigiLocker in R2 · Packaging Core · Requirements
FR-EMP-*,BR-EMP-*,RULE-EMP-*in the PRD and BRD · Related Organization, Identity & Access, Exit, hire-to-retire
1. Overview and scope
EMP is the system of record for people. It holds the employee's identity, personal and statutory details, and an effective-dated job history (department, designation, location, manager, grade, employment type, cost centre). Every other module asks EMP "who is this person and where do they sit on date D".
In scope: new hire creation; pre-boarding self-service link; joining formalities; employee directory; profile sections (personal, contact, family, education, experience, bank, documents, statutory IDs); job assignments with transfers, promotions and manager changes; probation and confirmation; onboarding and task checklists with owners; document collection and approval; induction programmes; letter templates (appointment, confirmation, transfer, relieving data to EXT); employee self-edit through change requests; custom fields; bulk Excel import with dry-run; DPDP consent capture.
Out of scope: recruitment/ATS and offers before hire (later), performance management (later), assets (later; checklist items in R1).
2. Learned from the reference product
Source: Spryple analysis, §4 and §21.2.
- [Observed] HR adds a new hire (name, hired-on, joining date, designation, mobile, emergency contact); the hire gets a self-onboarding link to fill personal, family, education, experience and documents; the link expires after 24 hours (SP-EMP-04, SP-EMP-05; §21.2).
- [Observed] Checklists by department are assigned to each new hire; items carry a department and an approver; "No Due" completes the checklist (SP-EMP-01, SP-EMP-07; §21.2).
- [Observed] Document approval loops with emails and re-upload; documents uploaded by HR are auto-approved (SP-EMP-08).
- [Observed] Induction programmes with type, department, conducted-by, date and time (SP-EMP-09; §21.2).
- [Observed] Probation is set in two places with different units (months in Onboarding Settings, days in HR rules 11–12) (defect D4, §17).
- [Inferred] Promotions and transfers are a "Contract/Promotions" tab without effective-dated history (§4.5).
3. Our design decisions
| ID | Decision [Proposed] | Reason |
|---|---|---|
| D-EMP-01 | emp_job_assignment is effective-dated; every change (transfer, promotion, manager change, grade change) is a new row with a reason and an effective date, optionally future-dated. | History-correct policies, payroll and reports. |
| D-EMP-02 | One Lifecycle Policy (probation length in days, confirmation workflow, notice period by grade or employment type) assigned by applicability. | One place for each rule (fixes D4). |
| D-EMP-03 | A generic task engine for checklists: templates with items, each item with an owner (role or person), due offset and optional evidence. Reused by EXT clearance. | One mechanism for onboarding, offboarding and ad-hoc tasks. |
| D-EMP-04 | Pre-boarding links are single-use tokens with a configurable expiry (default 7 days) and OTP to the candidate's mobile. | 24 hours is too short for candidates; OTP protects personal data. |
| D-EMP-05 | Employee self-edits are change requests with approval for sensitive fields (bank, name, PAN); low-risk fields update directly. | Prevents salary diversion fraud via bank changes. |
| D-EMP-06 | Aadhaar, PAN and bank account numbers use envelope encryption and are masked by default. Aadhaar is stored only where a statutory purpose exists, with consent. | DPDP and Aadhaar storage rules. |
| D-EMP-07 | Imports are versioned templates with validation, dry-run, an error file and an idempotent commit. | Migration is where SME projects fail. |
4. Domain model
5. State machines
5.1 Employee
5.2 Document and change request
6. Key business logic
6.1 Job assignment as of a date
jobAsOf(employee, date):
return emp_job_assignment where employee_id = employee
and valid_from <= date and (valid_to is null or valid_to >= date)
insertChange(employee, change, effective_date):
close the current row at effective_date - 1 ; insert new row from effective_date
if a future-dated row exists after effective_date: split or reject per user choice
emit emp.job.changed(employee, effective_date, changed fields)
# ATT and LVE re-resolve policies from effective_date; PAY checks structure suggestion
The reporting-manager chain for TEAM scope is the transitive closure over manager_employee_id for rows valid today. A cycle check runs on insert.
6.2 Probation and confirmation
on employee joined:
p = lifecyclePolicy(employee)
if p.probation_days > 0: status = OnProbation ; due = joining_date + p.probation_days
schedule reminder to manager at due - p.reminder_days_before
on due date: create confirmation request (workflow: manager then HR)
approve -> Confirmed, confirmation letter generated, emp.employee.confirmed
extend -> new due date (max p.max_extensions)
6.3 Import (dry-run then commit)
import(file, template_version, dry_run):
rows = parse ; for each row validate types, masters (location, department, designation
exist and active), uniqueness (employee code, work email, PAN), manager references
build plan: creates, updates, unchanged, errors
if dry_run: return plan and an error file
commit in batches with an idempotency key per row (employee code)
emit emp.import.committed with counts
7. Configuration
| Setting | Type | Default | Description |
|---|---|---|---|
preboarding.link_expiry_days | integer | 7 | Validity of the self-onboarding link |
preboarding.sections | array of string | all | Sections the candidate fills |
lifecycle.probation_days | integer | 180 | Probation length (single unit: days) |
lifecycle.reminder_days_before | integer | 15 | Confirmation reminder |
lifecycle.max_extensions | integer | 2 | Probation extensions |
lifecycle.notice_days | integer | 30 | Default notice period; overridable by grade |
self_edit.direct_fields | array of string | contact, emergency, photo | Updated without approval |
self_edit.approval_fields | array of string | bank, name, PAN, address | Need a change request |
documents.required_types | array of string | PAN, ID proof, education | Must be approved before Joined |
checklist.auto_reminders | boolean | true | Reminders for pending tasks [Observed SP-EMP-03] |
invite_on | enum | JOINED | HIRED, PREBOARDING_SUBMITTED or JOINED |
8. Screens by persona
| Persona | Web | Mobile |
|---|---|---|
| Candidate | Pre-boarding form (OTP), document upload, consent | Same, responsive |
| Employee | My profile (sections), change requests, documents, checklist status, induction schedule, letters | Profile, documents, letters |
| Line manager | Team directory, team members' job history, confirmation approvals, onboarding tasks they own | Approvals |
| HR manager | New hires (Hired/Joined filters), joining formalities, directory, transfers and promotions (with effective date), document approvals, checklists, induction programmes, letters, import | — |
| HR admin | Lifecycle policy, checklist templates, letter templates, document types, custom fields | — |
9. Events
| Event | Consumers |
|---|---|
emp.employee.hired | NTF (pre-boarding link), RPT |
emp.preboarding.submitted | NTF (HR task) |
emp.employee.joined | IAM (create user, invite), ATT (start computing days), LVE (pro-rata accrual), PAY (compensation required), LMS (mandatory courses), ENG (welcome) |
emp.job.changed | ATT, LVE, PAY, IAM (team scope), RPT |
emp.employee.confirmed | LVE (eligibility), NTF |
emp.document.submitted, emp.document.approved, emp.document.rejected | NTF |
emp.change_request.approved | PAY (bank change for the next run), NTF |
emp.task.overdue | NTF (reminder, escalation) |
emp.onboarding.completed | NTF, RPT |
emp.import.committed | RPT, IAM (bulk invites) |
emp.employee.separated | All modules (closing) |
Consumed: ext.separation.approved (status NoticePeriod, LWD set), ext.lwd.reached (status Separated), iam.user.activated (checklist item), org.department.deactivated (reassignment).
10. API surface
| Resource | Main operations |
|---|---|
/v1/emp/employees | POST new hire, GET directory (scoped), GET /:id, PATCH /:id |
/v1/emp/employees/:id/job-assignments | GET history, POST change with effective date |
/v1/emp/employees/:id/personal, /family, /education, /experience, /bank-accounts | Section CRUD |
/v1/emp/preboarding/:token | GET, PUT (candidate) |
/v1/emp/documents | POST upload, POST /:id/approve, POST /:id/reject |
/v1/emp/checklist-templates, /v1/emp/tasks | CRUD, POST /tasks/:id/complete |
/v1/emp/change-requests | POST, GET |
/v1/emp/letters, /v1/emp/letter-templates | Generate, list, CRUD |
/v1/emp/induction-programs | CRUD, assign |
/v1/emp/imports | POST with dry_run, GET /:id/errors |
Detailed in API endpoints.
11. Permissions
| Permission | Scopes | Default roles |
|---|---|---|
emp.employee.read | SELF, TEAM, LOCATION, ORG | all (scoped) |
emp.employee.create | ORG, LOCATION | hr_manager |
emp.employee.update | SELF (via change request), ORG | employee, hr_manager |
emp.job.change | ORG, LOCATION | hr_manager |
emp.sensitive.read | SELF, ORG | employee (SELF), hr_manager, payroll_manager |
emp.document.approve | ORG, LOCATION | hr_manager |
emp.task.complete | own tasks | task owners |
emp.checklist.manage | ORG | hr_admin |
emp.letter.generate | ORG | hr_manager |
emp.import.run | ORG | hr_admin |
emp.policy.manage | ORG | hr_admin |
12. Reports
| Report | Contents | Reference |
|---|---|---|
| Employee report with column chooser | Any profile and job field, filters, export | [Observed] SP-RPT-01, §21.2 |
| Joiners and leavers | By month, location, department | [Observed] SA dashboard |
| Onboarding status | Pending tasks by owner, overdue | [Observed] SP-EMP-07 |
| Probation due | Confirmations due in the next N days | [Proposed] |
| Job history | Transfers, promotions, manager changes | [Proposed] |
| Document compliance | Missing or rejected required documents | [Proposed] |
| Headcount trend | Active headcount at month end | [Proposed] |
13. Release and priority
| Capability | Release | MoSCoW |
|---|---|---|
| New hire, pre-boarding link, joining formalities, directory, profile sections | R1 | Must |
| Effective-dated job assignments, transfers, promotions, manager change | R1 | Must |
| Checklists with owners, document approval, induction | R1 | Must |
| Lifecycle policy (probation, confirmation, notice) | R1 | Must |
| Change requests for sensitive self-edits | R1 | Must |
| Import with dry-run | R1 | Must |
| Letter templates (appointment, confirmation) | R1 | Should |
| Custom fields on employee | R1 | Should |
| Org chart | R2 | Should |
| E-sign, DigiLocker, PAN verification | R2 | Could |
| Assets register | R3 | Could |
14. Open questions
- Is the
employee_codeimmutable after a transfer to a location with a different ID prefix? The default is immutable. - Do we capture Aadhaar at all in R1, given storage restrictions? The default is optional, masked and consent-backed; verify with counsel.
- Should rehire reuse the old employee record (with a new employment spell) or create a new one? The default is a new spell on the same person record (R2).