# Pay Revision — Stakeholder Flow & Process Guide

## 1. What Is a Pay Revision?

A Pay Revision records a structured change to an employee's pay components effective from a specific date. Each revision defines:
- **Which scope** of employees is affected (Overall → OU → PayConfig → PayGroup → DAGroup → Individual)
- **What kind** of revision it is (Nature: e.g. Normal, Arrear)
- **When** it takes effect (EffectiveFrom / EffectiveTo)
- **Pay components** (C2C, CostPerHour, RatePerHour, plus add-on components)

Revisions form a **date-ordered chain per scope+nature**. When a new revision is created, the system automatically closes (expires) the prior revision by setting its EffectiveTo to one day before the new revision's EffectiveFrom. This ensures no gaps and no overlaps are possible through normal operations.

---

## 2. Scope Levels

| Level | Applicable Value | Scope | Filter Criteria |
|-------|-----------------|-------|----------------|
| Overall | 0 | All employees across the organisation | None (nature filter only) |
| Organisational Unit | 1 | All employees in a specific OU | `OUID` |
| Pay Configuration | 2 | All employees under a pay configuration | `PAYCONFIGURATIONID` |
| Pay Group | 3 | All employees in a pay group within an OU | `PAYGROUPID + OUID` |
| DA Point Group | 4 | All employees in a DA group within an OU | `DAGROUPID + OUID` |
| Individual Employee | 5 | A single specific employee | `EMPLOYEEID + PAYCONFIGURATIONID` |

**Important:** Scope levels are independent chains. A revision at Employee level does not replace or interact with a revision at OU level — payroll processing resolves which revision applies based on its own priority rules.

---

## 3. Stakeholder Roles

| Role | Responsibilities |
|------|-----------------|
| HR Manager | Create, update, and delete pay revisions; determine scope and effective dates |
| Payroll Admin | Run pay processing that consumes revisions; cannot delete processed revisions |
| Employee | View their own revision history and current pay structure |
| System | Auto-number revisions, expire prior revisions, publish events, enforce chain integrity |

---

## 4. Revision Status Values

| Status | Value | Meaning |
|--------|-------|---------|
| Active | 1 | Current effective revision for the scope |
| Expired | 4 | Superseded by a newer revision; no longer active |

---

## 5. Core Flows

### 5.1 Create New Pay Revision

```
HR Manager
    │
    ├─ Opens Pay Revision form
    ├─ Selects Applicable scope (Overall / OU / PayConfig / PayGroup / DAGroup / Employee)
    ├─ Selects Nature (revision type)
    ├─ Enters RevisionDate, EffectiveFrom, EffectiveTo (optional)
    ├─ Enters PayReferenceNumber and ReferenceDate
    ├─ Enters pay component values (C2C, CostPerHour, RatePerHour, add-ons)
    ├─ Submits
    │
System (within single DB transaction)
    ├─ Validates:
    │     RevisionDate is required
    │     EffectiveFrom is required
    │     EffectiveTo (if provided) must be ≥ EffectiveFrom
    ├─ Generates auto-number (PayRevisionNumber)
    ├─ Assigns PayRevisionId
    ├─ Calls BaseEntityAppService pipeline (validation qualifiers, workflow, pre-hooks)
    ├─ Inserts row into TPAYREVISION (Status=1, Active)
    ├─ Finds all prior active revisions for the same scope+nature
    │     that overlap with the new EffectiveFrom
    ├─ For each overlapping prior revision:
    │     Sets EFFECTIVETO = NewEffectiveFrom - 1
    │     Sets STATUS = 4 (Expired)
    ├─ Commits transaction
    ├─ Publishes Dapr event (PayRevision saved)
    │
HR Manager
    └─ Receives confirmation with new PayRevisionId
```

### 5.2 Update Existing Pay Revision

```
HR Manager
    │
    ├─ Opens existing revision by PayRevisionId
    ├─ Modifies allowed fields (dates, amounts, remarks)
    ├─ Submits
    │
System (within single DB transaction)
    ├─ Validates same rules as Create
    ├─ Updates TPAYREVISION row
    ├─ Re-runs expiration logic:
    │     Finds prior active revisions for same scope+nature
    │     overlapping updated EffectiveFrom
    │     Expires them (EFFECTIVETO = UpdatedEffectiveFrom - 1, STATUS = 4)
    ├─ Commits transaction
    ├─ Publishes Dapr event
    │
HR Manager
    └─ Receives update confirmation
```

### 5.3 Delete Pay Revision

This is the most constrained operation. Deletion follows strict rules to maintain chain integrity.

```
HR Manager
    │
    ├─ Requests deletion of PayRevisionId
    │
System (pre-transaction checks)
    ├─ Check 1 — Pay Processing Guard:
    │     Query TPAYPROCESS for rows referencing this PayRevisionId
    │     If count > 0 → BLOCK
    │     Error: "Cannot delete: pay processing has already been completed for this revision."
    │
    ├─ Load the revision to inspect scope, nature, and EffectiveFrom
    │
    ├─ Begin DB transaction
    │
    ├─ Check 2 — Chain Position Guard:
    │     Query for any non-expired revision (STATUS ≠ 4) in the same scope+nature
    │     with EFFECTIVEFROM > this revision's EFFECTIVEFROM
    │     If count > 0 → ROLLBACK, BLOCK
    │     Error: "Cannot delete this revision as a newer revision exists.
    │             Delete the newest revision first."
    │
    ├─ Find Predecessor:
    │     Query for revision with the highest EFFECTIVEFROM that is still
    │     less than the deleted revision's EFFECTIVEFROM, same scope+nature
    │     (includes STATUS=4 records — predecessor was expired when this was created)
    │
    ├─ Delete TPAYREVISIONADDON rows for this PayRevisionId
    ├─ Delete TPAYREVISION row for this PayRevisionId
    │
    ├─ Restore Predecessor (if found):
    │     SET EFFECTIVETO = '9999-12-31'
    │     SET STATUS = 1 (Active)
    │
    ├─ Commit transaction
    │
HR Manager
    └─ Receives deletion confirmation
```

### 5.4 Payroll Processing Consumes Revision

```
Payroll Admin
    │
    ├─ Initiates pay processing run for a period
    │
Payroll Processing Engine
    ├─ For each employee in the run:
    │     Resolves the effective revision (GetPayRevisionByScope):
    │       Finds the revision with highest EFFECTIVEFROM ≤ PayPeriodDate
    │       for the relevant scope+nature combination
    │     Applies revision pay components to calculate net salary
    ├─ Records result in TPAYPROCESS (referencing PayRevisionId)
    │
    [Post-processing]
    │
    ├─ The referenced PayRevisionId is now locked for deletion
    │
Payroll Admin
    └─ Payslips generated; revision cannot be deleted
```

### 5.5 View Revision History

```
HR Manager / Employee
    │
    ├─ Requests revision list (GetPayRevisionList)
    │     Returns all revisions for the tenant, ordered by PayRevisionId
    │
    ├─ Selects a specific revision (GetPayRevision)
    │     Returns full detail with joined lookup names
    │     (OU, Employee, PayConfig, DAGroup, PayGroup, BizTransactionType)
    │
    └─ Views the revision chain for a scope via the list
```

---

## 6. Edge Cases & Special Behaviours

### 6.1 Overlapping Revision Periods

**Scenario:** Employee E has revision R1 from 2024-01-01 to open-ended (STATUS=1). HR creates R2 for the same scope, effective 2024-07-01.

**What the system does:**
- Saves R2 as STATUS=1 with EFFECTIVEFROM=2024-07-01
- Automatically sets R1: EFFECTIVETO=2024-06-30, STATUS=4

**Result:** No overlap exists. R1 covers Jan–Jun, R2 covers Jul onwards.

**Note:** The prior revision's EffectiveTo is always set regardless of what value it had before. If R1 originally had EffectiveTo=2025-12-31 and R2 starts 2024-07-01, R1 gets truncated to 2024-06-30.

---

### 6.2 Gap Between Revisions

**Scenario:** HR creates R2 for scope S with EffectiveFrom=2025-01-01, but R1 ends on 2023-12-31 (set by an even older R0 being saved). The period 2024-01-01 to 2024-12-31 has no revision.

**What the system does:** Nothing special. Gaps are a valid data state. The payroll engine is responsible for deciding which revision to apply when no revision is in effect for a given period (typically falls back to a wider scope or a default).

**HR Action Required:** If a gap is unintentional, HR must create an additional revision to cover the missing period.

---

### 6.3 Delete the Newest (Last) Revision

**Scenario:** Employee E has chain R1 (2023-01-01, expired) → R2 (2024-01-01, active). HR deletes R2.

**What the system does:**
1. Confirms R2 has not been processed
2. Confirms R2 is the newest (no revision with EFFECTIVEFROM > R2.EFFECTIVEFROM exists)
3. Finds R1 as the predecessor (highest EFFECTIVEFROM < R2.EFFECTIVEFROM)
4. Deletes R2's add-ons and R2 itself
5. Restores R1: EFFECTIVETO='9999-12-31', STATUS=1

**Result:** R1 is the active revision again, as if R2 was never created.

---

### 6.4 Attempt to Delete a Middle Revision

**Scenario:** Employee E has chain R1 (expired) → R2 (expired) → R3 (active). HR tries to delete R2.

**What the system does:**
- Detects that R3 exists with EFFECTIVEFROM > R2.EFFECTIVEFROM and STATUS ≠ 4
- **BLOCKS the deletion**
- Error: "Cannot delete this revision as a newer revision exists. Delete the newest revision first."

**HR Action Required:** Delete R3 first (which restores R2), then delete R2 (which restores R1), and so on.

---

### 6.5 Delete the Only Revision (No Predecessor)

**Scenario:** Employee E has only one revision R1 (active) which has never been processed.

**What the system does:**
1. Confirms no pay processing
2. Confirms R1 is the newest (no newer exists)
3. No predecessor found
4. Deletes R1 and its add-ons
5. No restoration step (nothing to restore)

**Result:** The employee has no revision on record.

---

### 6.6 Attempt to Delete a Processed Revision

**Scenario:** R1 has been used in a pay processing run. HR tries to delete R1.

**What the system does:**
- Queries TPAYPROCESS: finds a record referencing R1
- **BLOCKS immediately** before any transaction begins
- Error: "Cannot delete: pay processing has already been completed for this revision."

---

### 6.7 Same Scope, Different Natures — Independent Chains

**Scenario:** OU = HQ has two revisions:
- R1 (Nature=1, Normal): 2024-01-01, active
- R2 (Nature=2, Arrear): 2024-03-01, active

HR creates R3 (Nature=1, Normal): 2024-06-01 for OU=HQ.

**What the system does:**
- Expires R1 (same nature=1, same scope) → R1.EFFECTIVETO = 2024-05-31, STATUS=4
- Does NOT touch R2 (different nature — separate chain)

**Result:** Nature=1 chain and Nature=2 chain are fully independent revision histories.

---

### 6.8 Minimum Wage Import

**Scenario:** HR imports revised minimum wage pay structures in bulk.

**What the system does:**
- For each revision in the import batch, the `IsMinimumWageImport` flag is set to `true`
- BLL detects this flag and applies minimum-wage-specific deduction logic in addition to the standard pay revision logic
- All minimum wage revisions participate in the same save pipeline, transaction, and expiration logic as regular revisions

---

### 6.9 Overall-Level Revision (Applicable = 0)

**Scenario:** HR creates an overall revision that applies to the entire organisation.

**What the system does:**
- No scope filter applied in expiration query (no EmployeeId, OUId, etc.)
- Any other overall revision with the same Nature that overlaps is expired
- Scope-specific revisions (Employee, OU, etc.) are NOT affected

---

## 7. System Integration Points

### 7.1 Auto Numbering
- Every new revision receives a system-generated `PayRevisionNumber` via the `AutoNumber` service
- If the save fails after number generation, the number is rolled back to prevent gaps

### 7.2 BaseEntityAppService Pipeline
Every save/update passes through:
1. Validation qualifier checks
2. Workflow state checks (approval rules if configured)
3. Pre-persist hooks
4. DB INSERT/UPDATE (inside the pipeline's transaction)
5. Prior-revision expiration (inside same transaction)
6. Post-persist hooks
7. Dapr outbox event publishing (asynchronous delivery guaranteed)

### 7.3 Cache
- `GetPayRevision` (single record) is cached at `CLIENT_LEVEL`
- Cache is invalidated after every Save, Update, and Delete

### 7.4 Audit Trail
- `CreatedById`, `CreatedOn`, `ModifiedById`, `ModifiedOn` tracked on every row
- Dapr event log published for every create/update/delete (non-repudiation)

---

## 8. Revision Chain Diagram

```
Time →──────────────────────────────────────────────────────────────>

R1  [■■■■■■■■■■■■■■■]  EffectiveFrom=Jan  EffectiveTo=Jun  STATUS=4 (Expired by R2)
                        [▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶]
R2                      EffectiveFrom=Jul  EffectiveTo=∞   STATUS=1 (Active)

After saving R3 (EffectiveFrom=Oct):

R1  [■■■■■■■■■■■■■■■]
                       [■■■■■■■■■■]
R2                     EffectiveTo=Sep    STATUS=4 (Expired by R3)
                                         [▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶]
R3                                       EffectiveFrom=Oct EffectiveTo=∞ STATUS=1

After deleting R3:

R1  [■■■■■■■■■■■■■■■]
                       [▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶▶]
R2  (Restored)         EffectiveTo=∞  STATUS=1

■ = Expired period   ▶ = Active open-ended period
```

---

## 9. Summary Table — Allowed Operations by State

| Operation | Condition | Allowed? | System Action |
|-----------|-----------|----------|--------------|
| Create | Always | Yes | Auto-expires prior overlapping revisions |
| Update | Always | Yes | Re-expires prior overlapping revisions |
| Delete | Revision has pay processing records | **No** | Error: processing exists |
| Delete | Revision is a middle revision | **No** | Error: newer revision exists |
| Delete | Revision is the newest (has predecessor) | Yes | Restores predecessor to active |
| Delete | Revision is the only one for scope+nature | Yes | No restoration needed |
| View | Always | Yes | Returns full detail with lookups |
| Picklist | Always | Yes | Paginated list of Id + Number |
