← All posts

2026-06-18

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

Spring BootHRISDomain DesignPostgreSQLPRANI OPS

PRANI OPS HR Actions table with promotions in APPROVED and EFFECTIVE status

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:

  1. Action templates (playbook + default payload).
  2. A lifecycle: draft → submit → approve → effective-date processing.
  3. A processor that applies org and compensation fields from the payload.
  4. 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)

StatusMeaning
DRAFTEditable; not in the approval queue
SUBMITTEDWaiting on approver
APPROVEDAllowed to process when due
EFFECTIVEProcessor 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 / title
  • department
  • grade / pay band
  • flsaStatus
  • 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:

  1. Sign in as HR (hr@demo…).
  2. Action templates → Promotion → Use template (or create via API with a rich payload).
  3. Submit → Approve → Process due.
  4. 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.