2026-06-18
How to Make HR “Actions” Update the Employee Record (Not Just Forms)

Part of Building PRANI OPS — Technical Notes. See also the PRANI OPS case study · previous: private demo hosting.
Introduction
The hook
A lot of HR demos stop at “we saved a row.”
Status flips to APPROVED. Everyone claps. The employee’s job title on the profile? Still “Analyst.” Pay band? Untouched. That is not an HR action — that is a form with good branding.
On PRANI OPS I wanted the opposite story for recruiters and for myself: Promotion → Process due → the employee record actually changes.
The objective
By the end of this note you’ll see how we wire:
- Action templates (playbook + default payload).
- A lifecycle: draft → submit → approve → effective-date processing.
- A processor that applies org and compensation fields from the payload.
- Tests that treat side effects as the contract — not the happy-path HTTP 200 alone.
Prerequisites
- Basic REST + relational data modeling
- Comfort with “effective dating” (when a change should apply)
- Optional: Spring service layer patterns
Body
Why templates exist
HR does not invent every promotion from a blank screen. They reuse a catalog: Promotion, Merit increase, Lateral transfer, and so on.
Each template carries:
- An action type (e.g.
PROMOTION) - A payload (JSON hints — “includes comp change,” default fields)
- A short playbook (human checklist: update title, notify IT, …)
“Use template” opens HR Actions with that type and payload pre-loaded. The human still picks the employee and effective date.
The lifecycle (keep it boring)
| Status | Meaning |
|---|---|
DRAFT | Editable; not in the approval queue |
SUBMITTED | Waiting on approver |
APPROVED | Allowed to process when due |
EFFECTIVE | Processor ran; employee/comp updated |
Process due is the important button (or scheduled job). It finds approved actions whose effective date is today or earlier and runs the processor.
If you skip that step, you only have workflow theater.
What the processor actually does
For movement types (promotion, reassignment, reclass), we apply payload fields onto the employee when present:
jobTitle/titledepartmentgrade/ pay bandflsaStatus- optional
managerId - if
includesCompChange+amountCents→ write a compensation record
Sketch of the idea (not the full service):
// Conceptual — apply org fields from action payload
String jobTitle = payloadString(payload, "jobTitle", "job_title", "title");
if (jobTitle != null) {
employee.setJobTitle(jobTitle);
}
String department = payloadString(payload, "department");
if (department != null) {
employee.setDepartment(department);
}
// … grade, FLSA, manager, then optional compensation write
The UI does not need twenty extra form fields for a demo-quality promotion if the template (or API create) already carries the payload. What matters is that processing reads that payload and mutates durable state.
Prove it with a path recruiters can repeat
On the hosted demo:
- Sign in as HR (
hr@demo…). - Action templates → Promotion → Use template (or create via API with a rich payload).
- Submit → Approve → Process due.
- Open the employee — title / department / grade / pay should match what you put in the payload.
If step 4 fails, the feature is not done, no matter how pretty the table looks.
Tests are the product contract
We added integration coverage for the promotion path: create with payload → approve → process → assert employee and compensation fields. That keeps “Process due” from regressing into a no-op during later refactors.
Conclusion
Summary
- Status fields are not side effects.
- Templates give you playbooks and defaults; Process due applies reality.
- Put the meaningful fields in the payload and make the processor the single writer for effective changes.
- Demo + integration test > slideware.
Call to action
Walk the path on demo.darrellkilo.com or read the fuller product story on the PRANI OPS case study.
Next in the series: the time we stored user IDs in a table that expected employee IDs — and how that broke “acting for” leave approvals.