Skip to content

feat(payroll): effective-dated salary component allocation on SSA - #2

Merged
sivajyothis7 merged 1 commit into
developfrom
feat/ssa-component-allocation
Sep 1, 2026
Merged

sivajyothis7 merged 1 commit into
developfrom
feat/ssa-component-allocation

Conversation

@sivajyothis7

Copy link
Copy Markdown
Contributor

Per-component salary amounts on the Salary Structure Assignment, changeable from a date, and read correctly by payroll for each period.

Each component gets its own Currency field on the assignment (HRA, Transport, Other, plus core base, and a read-only Total). On the SSA form each carries a history button and a change button.

Changing a component does not rewrite the submitted assignment. It creates the next one: a new submitted SSA dated from the effective date, carrying every other value forward including payroll_payable_account, without which a Payroll Entry silently drops the employee.

That is what makes payroll correct. hrms picks the assignment for a slip with from_date <= <slip start> ORDER BY from_date DESC and merges every field of that row into the salary-formula namespace, so a slip reads the amounts in force for its own period and regenerating an old slip reproduces its original figures.

Notes on the mechanics:

  • Mid-year tax openings (taxable_earnings_till_date, tax_deducted_till_date) are cleared on the new assignment. get_opening_for() only ignores them while from_date < payroll_period.start_date, so carrying them into a later assignment in the same period would re-arm a stale opening on every remaining slip.
  • docstatus is reset explicitly after copy_doc: frappe.copy_doc only clears it when local.flags.in_test is false, so tests would otherwise insert straight at docstatus 1 and skip the draft-then-submit path.
  • Retro increments stay allowed - dating a change before already-paid periods is normal - but the call reports which periods it reached behind, since no arrears are created automatically.
  • A change is refused only when it would be meaningless: an assignment already starts on that date, or a later one already exists.
  • wire_salary_structure_formulas() points the Salary Structure earning rows at these fields, dry-run by default. Without it the values are stored and never read. It writes Salary Detail rows directly because amount_based_on_formula is not allow_on_submit and amending the structure would break linked assignments.

The component fields ship as fixtures filtered on module TSI, and sit in their own section after payroll_cost_centers so they do not absorb core's fields.

Not included, by design: the HR Suite Salary Breakup Table band lookup, the override ledger with its Pending/Applied/Superseded states, the daily scheduler, and any write into a submitted assignment.

Per-component salary amounts on the Salary Structure Assignment, changeable
from a date, and read correctly by payroll for each period.

Each component gets its own Currency field on the assignment (HRA, Transport,
Other, plus core `base`, and a read-only Total). On the SSA form each carries a
history button and a change button.

Changing a component does not rewrite the submitted assignment. It creates the
next one: a new submitted SSA dated from the effective date, carrying every
other value forward including payroll_payable_account, without which a Payroll
Entry silently drops the employee.

That is what makes payroll correct. hrms picks the assignment for a slip with
`from_date <= <slip start> ORDER BY from_date DESC` and merges every field of
that row into the salary-formula namespace, so a slip reads the amounts in force
for its own period and regenerating an old slip reproduces its original figures.

Notes on the mechanics:

* Mid-year tax openings (taxable_earnings_till_date, tax_deducted_till_date) are
  cleared on the new assignment. get_opening_for() only ignores them while
  from_date < payroll_period.start_date, so carrying them into a later
  assignment in the same period would re-arm a stale opening on every remaining
  slip.
* docstatus is reset explicitly after copy_doc: frappe.copy_doc only clears it
  when local.flags.in_test is false, so tests would otherwise insert straight at
  docstatus 1 and skip the draft-then-submit path.
* Retro increments stay allowed - dating a change before already-paid periods is
  normal - but the call reports which periods it reached behind, since no
  arrears are created automatically.
* A change is refused only when it would be meaningless: an assignment already
  starts on that date, or a later one already exists.
* wire_salary_structure_formulas() points the Salary Structure earning rows at
  these fields, dry-run by default. Without it the values are stored and never
  read. It writes Salary Detail rows directly because amount_based_on_formula is
  not allow_on_submit and amending the structure would break linked assignments.

The component fields ship as fixtures filtered on module TSI, and sit in their
own section after payroll_cost_centers so they do not absorb core's fields.

Not included, by design: the HR Suite Salary Breakup Table band lookup, the
override ledger with its Pending/Applied/Superseded states, the daily scheduler,
and any write into a submitted assignment.
@sivajyothis7
sivajyothis7 merged commit 8edd0d4 into develop Sep 1, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant