# Pay Revision — Gherkin Test Cases

---

## Feature: Create Pay Revision

### Background
```
Given the user is authenticated as an HR Manager
And the system has auto-numbering configured for "PayRevision"
```

---

### Scenario: Create the first revision for an employee (no prior revision)
```gherkin
Given no revision exists for Employee "E001" with Nature "Normal" and PayConfiguration "PC01"
When the HR Manager submits a new Pay Revision with:
  | EmployeeId       | E001       |
  | PayConfigId      | PC01       |
  | Applicable       | 5 (Employee) |
  | Nature           | Normal     |
  | RevisionDate     | 2025-06-01 |
  | EffectiveFrom    | 2025-07-01 |
  | C2C              | 60000      |
Then a new revision record is created in TPAYREVISION with STATUS = 1
And the PayRevisionNumber is auto-generated
And the EffectiveTo is open-ended (9999-12-31)
And the response includes the new PayRevisionId
And a Dapr event is published for the create operation
```

---

### Scenario: Create a revision that overlaps an existing active revision
```gherkin
Given Employee "E001" with Nature "Normal" has an active revision R1:
  | EffectiveFrom | 2025-01-01 |
  | EffectiveTo   | 9999-12-31 |
  | STATUS        | 1          |
When the HR Manager creates a new revision R2 for the same scope with:
  | EffectiveFrom | 2025-07-01 |
Then R2 is saved with STATUS = 1 and EFFECTIVEFROM = 2025-07-01
And R1 is automatically updated with EFFECTIVETO = 2025-06-30 and STATUS = 4
And only one revision has STATUS = 1 for that scope+nature
```

---

### Scenario: Create a revision with EffectiveTo before EffectiveFrom
```gherkin
Given the HR Manager prepares a revision with:
  | EffectiveFrom | 2025-07-01 |
  | EffectiveTo   | 2025-06-30 |
When the HR Manager submits the revision
Then the system rejects the request
And the error message is "Effective To must be on or after Effective From."
And no revision is saved
And no auto-number is consumed
```

---

### Scenario: Create a revision without a RevisionDate
```gherkin
Given the HR Manager prepares a revision with all fields except RevisionDate
When the HR Manager submits the revision
Then the system rejects the request
And the error message is "Revision Date is required."
And no revision is saved
```

---

### Scenario: Create a revision without an EffectiveFrom
```gherkin
Given the HR Manager prepares a revision with all fields except EffectiveFrom
When the HR Manager submits the revision
Then the system rejects the request
And the error message is "Effective From date is required."
And no revision is saved
```

---

### Scenario: Create a revision and auto-number rollback on save failure
```gherkin
Given the auto-number service has assigned PayRevisionId = 5001
And the database INSERT fails due to a constraint violation
When the save is attempted
Then the transaction is rolled back
And the auto-number "PayRevision" is rolled back to restore 5001
And no row exists in TPAYREVISION with PayRevisionId = 5001
```

---

### Scenario: Create an OU-level revision (Applicable = 1)
```gherkin
Given no revision exists for OU "HQ" with Nature "Normal"
When the HR Manager creates a revision with:
  | Applicable    | 1 (OU) |
  | OUId          | HQ     |
  | Nature        | Normal |
  | EffectiveFrom | 2025-01-01 |
Then a revision row is created with APPLICABLE = 1 and OUID = HQ's Id
And the EMPLOYEEID, PAYCONFIGURATIONID, DAGROUPID, PAYGROUPID columns are null/default
```

---

### Scenario: Create a revision for the same scope and different nature — independent chains
```gherkin
Given Employee "E001" has:
  | R1: Nature=Normal, EffectiveFrom=2025-01-01, STATUS=1 |
When the HR Manager creates a new revision for Employee "E001" with:
  | Nature        | Arrear     |
  | EffectiveFrom | 2025-01-01 |
Then the Arrear revision is created with STATUS = 1
And R1 (Nature=Normal) remains unchanged with STATUS = 1
And the expiration logic does not touch R1
```

---

### Scenario: Create a revision using the Minimum Wage Import flag
```gherkin
Given the HR Manager prepares a batch import of minimum wage revisions
When a revision is submitted with IsMinimumWageImport = true
Then the revision is saved through the standard save pipeline
And the BLL applies minimum-wage-specific deduction logic within the same transaction
And the revision record in TPAYREVISION is identical in structure to a normal revision
```

---

## Feature: Update Pay Revision

---

### Scenario: Update an existing revision's EffectiveFrom (shifts expiration boundary)
```gherkin
Given Employee "E001" has the chain:
  | R1: EffectiveFrom=2024-01-01, EffectiveTo=2024-12-31, STATUS=4 |
  | R2: EffectiveFrom=2025-01-01, EffectiveTo=9999-12-31, STATUS=1 |
When the HR Manager updates R2's EffectiveFrom to 2024-07-01
Then R2's EFFECTIVEFROM is updated to 2024-07-01
And R1 is re-expired with EFFECTIVETO = 2024-06-30
```

---

### Scenario: Update a revision's pay component amounts
```gherkin
Given revision R1 exists with C2C = 50000
When the HR Manager updates R1 with C2C = 60000
Then TPAYREVISION row for R1 has C2C = 60000
And MODIFIEDBYID and MODIFIEDON are updated
And a Dapr event is published for the update operation
```

---

## Feature: Delete Pay Revision

---

### Scenario: Delete the only revision for a scope (no predecessor)
```gherkin
Given Employee "E001" has exactly one revision R1 with STATUS = 1
And R1 has no records in TPAYPROCESS
When the HR Manager deletes R1
Then R1's add-on rows in TPAYREVISIONADDON are deleted first
And R1's row in TPAYREVISION is deleted
And no restoration step is attempted
And the response is the delete success message
```

---

### Scenario: Delete the newest revision when a predecessor exists
```gherkin
Given Employee "E001" has the chain:
  | R1: EffectiveFrom=2024-01-01, EffectiveTo=2024-12-31, STATUS=4 |
  | R2: EffectiveFrom=2025-01-01, EffectiveTo=9999-12-31, STATUS=1 |
And R2 has no records in TPAYPROCESS
When the HR Manager deletes R2
Then R2's TPAYREVISIONADDON rows are deleted
And R2's TPAYREVISION row is deleted
And R1 is restored with EFFECTIVETO = '9999-12-31' and STATUS = 1
And the response is the delete success message
```

---

### Scenario: Delete a revision in the middle of a chain (blocked)
```gherkin
Given Employee "E001" has the chain:
  | R1: EffectiveFrom=2023-01-01, EffectiveTo=2023-12-31, STATUS=4 |
  | R2: EffectiveFrom=2024-01-01, EffectiveTo=2024-12-31, STATUS=4 |
  | R3: EffectiveFrom=2025-01-01, EffectiveTo=9999-12-31, STATUS=1 |
When the HR Manager attempts to delete R2
Then the system rejects the request within the transaction
And the error is "Cannot delete this revision as a newer revision exists. Delete the newest revision first."
And no rows are deleted from TPAYREVISION or TPAYREVISIONADDON
And the revision chain is unchanged
```

---

### Scenario: Delete the oldest revision in a three-revision chain (blocked)
```gherkin
Given Employee "E001" has the chain:
  | R1: EffectiveFrom=2023-01-01, STATUS=4 |
  | R2: EffectiveFrom=2024-01-01, STATUS=4 |
  | R3: EffectiveFrom=2025-01-01, STATUS=1 |
When the HR Manager attempts to delete R1
Then the system rejects the request
And the error is "Cannot delete this revision as a newer revision exists. Delete the newest revision first."
```

---

### Scenario: Delete a processed revision (blocked at pay-process guard)
```gherkin
Given revision R1 has a record in TPAYPROCESS referencing its PayRevisionId
When the HR Manager attempts to delete R1
Then the system rejects the request before opening a transaction
And the error is "Cannot delete: pay processing has already been completed for this revision."
And no rows are deleted
```

---

### Scenario: Sequential deletion unwinds the chain correctly
```gherkin
Given Employee "E001" has the chain:
  | R1: EffectiveFrom=2023-01-01, EffectiveTo=2023-12-31, STATUS=4 |
  | R2: EffectiveFrom=2024-01-01, EffectiveTo=2024-12-31, STATUS=4 |
  | R3: EffectiveFrom=2025-01-01, EffectiveTo=9999-12-31, STATUS=1 |
And none have been processed

When the HR Manager deletes R3
Then R2 is restored with EFFECTIVETO = '9999-12-31' and STATUS = 1
And the chain is: R1 (expired) → R2 (active)

When the HR Manager deletes R2
Then R1 is restored with EFFECTIVETO = '9999-12-31' and STATUS = 1
And the chain is: R1 (active, only)

When the HR Manager deletes R1
Then no revision exists for that scope+nature
```

---

### Scenario: Delete is transactional — partial failure rolls back
```gherkin
Given revision R1 exists with R0 as predecessor
And deletion of R1's add-ons succeeds
But deletion of R1 itself fails due to a foreign key constraint
When the delete is attempted
Then the transaction is rolled back
And R1's TPAYREVISIONADDON rows still exist
And R0 has NOT been restored (restoration never ran)
And the chain is unchanged
```

---

## Feature: Revision Expiration (Effective Period Management)

---

### Scenario: Prior revision with a fixed EffectiveTo gets truncated by a new revision
```gherkin
Given Employee "E001" has R1 with:
  | EffectiveFrom | 2024-01-01 |
  | EffectiveTo   | 2024-12-31 |
  | STATUS        | 1          |
When a new revision R2 is created with EffectiveFrom = 2024-07-01
Then R1 is expired with EFFECTIVETO = 2024-06-30 and STATUS = 4
And R2 is created with EFFECTIVEFROM = 2024-07-01 and EFFECTIVETO = 9999-12-31
```

---

### Scenario: Multiple prior revisions are all expired when a new revision is created
```gherkin
Given Employee "E001" has two overlapping active revisions for the same scope:
  | R1: EffectiveFrom=2024-01-01, STATUS=1 |
  | R2: EffectiveFrom=2024-06-01, STATUS=1 |
  (data integrity anomaly — should not occur through normal UI)
When a new revision R3 is created with EffectiveFrom = 2024-09-01
Then both R1 and R2 are expired (EFFECTIVETO < 2024-09-01, STATUS=4)
And only R3 has STATUS = 1
```

---

### Scenario: Creating a revision with the same EffectiveFrom as an existing revision
```gherkin
Given Employee "E001" has R1 with EffectiveFrom = 2025-01-01 and STATUS = 1
When the HR Manager creates R2 for the same scope with EffectiveFrom = 2025-01-01
Then R1 is expired with EFFECTIVETO = 2024-12-31 and STATUS = 4
And R2 is created with EFFECTIVEFROM = 2025-01-01 and STATUS = 1
```

---

## Feature: Scope Isolation

---

### Scenario: OU-level expiration does not affect Employee-level revisions
```gherkin
Given OU "HQ" has revision R_OU with Nature "Normal" and STATUS = 1
And Employee "E001" (in HQ) has revision R_EMP with Nature "Normal" and STATUS = 1
When a new OU-level revision is created for OU "HQ" with same Nature
Then the old R_OU is expired
And R_EMP (Employee-level) is NOT touched
And R_EMP remains STATUS = 1 with its original dates
```

---

### Scenario: PayGroup-level revision does not expire DAGroup-level revision
```gherkin
Given PayGroup "PG01" (in OU HQ) has revision R_PG with Nature "Normal" and STATUS = 1
And DAGroup "DAG01" (in OU HQ) has revision R_DA with Nature "Normal" and STATUS = 1
When a new PayGroup-level revision is created for "PG01"
Then R_PG is expired
And R_DA is NOT expired (different scope level)
```

---

## Feature: Read Pay Revision

---

### Scenario: Get a single revision by ID — all joined fields returned
```gherkin
Given revision R1 exists with PayRevisionId = 1001
And it references EmployeeId = 50, OUId = 10, PayConfigId = 20
When the HR Manager calls GetPayRevision with PayRevisionId = 1001
Then the response includes:
  | PayRevisionId          | 1001            |
  | EmployeeCode           | from MEMPLOYEE  |
  | EmployeeName           | from MEMPLOYEE  |
  | OUCode                 | from MORGANIZATIONUNIT |
  | OUName                 | from MORGANIZATIONUNIT |
  | PayConfigurationCode   | from MPAYCONFIGURATION |
  | CreatedByName          | from MUSER      |
```

---

### Scenario: Get revision by ID — result is cached at CLIENT_LEVEL
```gherkin
Given revision R1 is fetched for the first time by the HR Manager
When the same revision R1 is requested again by the same tenant
Then the result is served from cache without a database round-trip
```

---

### Scenario: Cache is invalidated after a save
```gherkin
Given revision R1 is cached for Client "ACME"
When revision R1 is updated
Then the CLIENT_LEVEL cache entry for R1 is invalidated
And the next GetPayRevision call hits the database and returns updated data
```

---

### Scenario: Get revision list — returns all revisions for the tenant
```gherkin
Given revisions R1, R2, R3 exist (various statuses and scopes)
When the HR Manager calls GetPayRevisionList
Then all three revisions are returned ordered by PayRevisionId
```

---

### Scenario: Get select list — paginated picklist
```gherkin
Given 100 revisions exist
When GetSelectListPayRevision is called with FirstNumber=1 and MaxResult=10
Then exactly 10 rows are returned
And each row contains: Id, PayRevisionNumber, RevisionDate, PayReferenceNumber, ReferenceDate
When GetSelectListPayRevision is called with FirstNumber=-1 and MaxResult=-1
Then all 100 rows are returned
```

---

## Feature: Payroll Processing Integration

---

### Scenario: GetPayRevisionByScope returns the effective revision for a payroll run
```gherkin
Given Employee "E001" has the chain:
  | R1: EffectiveFrom=2024-01-01, EffectiveTo=2024-06-30, STATUS=4 |
  | R2: EffectiveFrom=2024-07-01, EffectiveTo=9999-12-31, STATUS=1 |
When the payroll engine queries GetPayRevisionByScope for period 2024-09-01
Then R2 is returned (highest EffectiveFrom ≤ 2024-09-01)
```

---

### Scenario: Deletion is blocked if revision has been used in payroll
```gherkin
Given revision R1 was used in the September 2024 payroll run
And a TPAYPROCESS record references R1's PayRevisionId
When the HR Manager attempts to delete R1
Then the system returns the error "Cannot delete: pay processing has already been completed for this revision."
And R1 remains intact
```

---

## Feature: Concurrent Operations (Race Conditions)

---

### Scenario: Two users create revisions for the same scope simultaneously
```gherkin
Given Employee "E001" has no active revision
When User A and User B both submit a new revision for Employee "E001" at the same time
Then exactly one revision succeeds and gains the auto-number
And the other fails on the transaction commit or constraint check
And the system does not have two active (STATUS=1) revisions for the same scope+nature
```

---

### Scenario: Delete chain guard is checked inside the transaction
```gherkin
Given revision R2 is the newest for Employee "E001"
And another user is creating R3 for the same employee concurrently
When User A's delete of R2 begins
Then the CHECK_NEWER_REVISION_EXISTS query runs inside the transaction
And if R3 has been committed before A's check, A's delete is blocked with the chain guard error
And if R3 commits after A's delete commits, R3 becomes the only active revision
```
