---
title: EAIP service period management
summary: Creates, validates, and maintains the EAIP service period that anchors every expense, wage subsidy, purchase, and bill for a claim.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.EAIP
reference_type: package
operation_id: OP-EAIP-01
trigger: Caller calls clsPeriod.NewPeriod or clsPeriod.GetPeriod (or the collection reader clsPeriod.GetPeriods), edits properties, and calls Save()
---

# EAIP service period management

The EAIP service period is the aggregate root of the whole library. Every purchase, wage subsidy, expense history row, billing record, and reimbursement match ultimately hangs off one `EAIP_PERIOD` row. This operation covers the full lifecycle of that row: sequence-driven creation with a default `New` status, aggravation-employer and concurrent-injury context, an eleven-rule validation set that guards service dates against the claim's injury date and against other periods on the same claim, the save cascade into purchases, wage subsidies, period history, and user history, and the delete path that relies on database triggers plus the package's own orphan cleanup.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-EAIP-01 |
| **Trigger** | Caller invokes `clsPeriod.NewPeriod(dpi, claimData)` for a new period, `clsPeriod.GetPeriod(dpi, periodID)` for one existing period, or `clsPeriod.GetPeriods(dpi, claimID)` for every period on a claim; edits properties; then calls `Save()` |
| **Inputs** | `IDataPortalInfo` (supplies the acting user and portal), claim data or `ClaimID`, EAIP specialist mail ID, service effective and expiration dates, end reason code, status code, note/job-duty/purchase-justification text, aggravation policy or provider location, concurrent-injury interruption dates, `UpdateNum` |
| **Outputs** | A persisted `EAIP_PERIOD` row; cascaded saves of purchases, wage subsidies, period-status history, and specialist-assignment history; a broken-rule collection for validation failures; a DB contention error when the affected row count is not one |
| **Primary components** | `clsPeriod` (`Create`, `BeforeInsert`/`AfterInsert`, `BeforeUpdate`/`AfterUpdate`, `BeforeDelete`/`AfterDelete`, `BeforeRetrieve`/`AfterRetrieve`, `AddBusinessRules`), `clsPeriod.clsAggravation`, `clsPeriod.clsConcurrentInjuriesInformation`, `clsPeriod.clsContactInfo`, `clsPeriodHistory`, `clsUserHistory`, `clsHistoryBase` |
| **Entry point** | `Business\SAIF.Business.Claim.EAIP\Period\clsPeriod.vb` |

## Steps

1. **Choose an entry point.** Three factories drive the operation:
   - `NewPeriod(inDPI, inClaimData)` builds a criteria, hands it to the portal's create, and lands in `Create()` (step 2).
   - `GetPeriod(inDPI, inPeriodID)` builds a criteria carrying the period identifier and lands in `BeforeRetrieve` (step 12).
   - `GetPeriods(inDPI, inClaimData)` / `GetPeriods(inDPI, inClaimID)` load a collection through the package read `Pk82500_eaip.eaip_period_select`, bound only by claim identifier ([SQL-021](../sql/SQL-021.md), [Details](../procedures/pk82500_eaip.md#eaip_period_select)).
2. **Reserve the identifier and seed the status.** `Create()` asks the portal for the next value of the `eaip_period_seq` sequence and stores it on the criteria ([SQL-004](../sql/SQL-004.md), [EAIP_PERIOD_SEQ](../database-tables.md#eaip_period_seq)). It then sets the status code to the `New` constant, sets the status date to today, and records the create user and timestamp from the portal info.
3. **Attach the claim context.** The claim data object supplied to `NewPeriod` — or reloaded on retrieve — carries the injury date, claim number, worker name, policy number, and federal/inmate indicators that most rules on this page compare against.
   > **Origin**: Claim identity, injury date, worker, and policy attributes are resolved by `View.Claim.clsClaimData.GetData` in `SAIF.Business.View.Claim` and executed on behalf of this operation. That library is documented in the previous format, so no per-operation page exists; see [Operations and processes](../../saif-business-view-claim/operations-processes.md) in the `SAIF.Business.View.Claim` documentation set.
4. **Set narrative text with curly-character scrubbing.** The note text, regular job duties, modified job duties, and purchase justification setters run every incoming string through a curly-character replacement that maps the Windows-1252 smart quotes and dashes (character codes 145, 146, 147, 148, 150) and the inverted question mark (191) to plain apostrophes, quotes, and hyphens before the value is stored. This keeps the Oracle `VARCHAR2` columns and the downstream billing XML free of characters that the print stream cannot render.*
5. **Record the aggravation employer, if any.** `clsAggravation` holds either a policy identifier or a provider-location identifier but never both: setting the policy identifier empties the provider-location identifier and vice versa. Reading the contact information resolves a policy contact through `View.Policy.clsPolicyData.GetPolicyData` or a provider contact through `Provider.clsProviderLocation.GetProviderLocation`, depending on which identifier is populated.
   > **Origin**: Policy name and address resolution is implemented in `SAIF.BOF.Business.Policy` and provider-location resolution in `SAIF.Business.View.Provider`, and both are executed on behalf of this operation. Those libraries are documented in the previous format, so no per-operation page exists; see [Operations and processes](../../saif-business-view-provider/operations-processes.md) in the `SAIF.Business.View.Provider` documentation set.
6. **Record concurrent-injury interruptions, if any.** `clsConcurrentInjuriesInformation` carries the interrupting claim number, the interrupted claim number, and the interruption effective and expiration dates, which are bound as ordinary parameters on insert and update.
7. **Validate before saving.** The rule set registered in `AddBusinessRules` runs on `IsValid`:
   - `Rule_ValidSpecialist` — an EAIP specialist mail ID is required ("Error: EAIP specialist is required.").
   - `Rule_ValidEffectiveDate` — the service start date must fall between the claim's injury date and the period end date.
   - `Rule_ValidExpirationDate` — the service end date must fall between the injury date and today.
   - `Rule_ValidServiceDates` — the period may not overlap any other period on the same claim; the rule re-queries `GetPeriods` for the claim and compares date ranges ([BR-PROC-013](../business-rules.md#br-proc-013-eaip-service-periods-for-a-claim-may-not-overlap), [SQL-021](../sql/SQL-021.md)).
   - `Rule_ValidHasExpenses` — a period must carry at least one wage subsidy or one purchase request.
   - `Rule_ValidEarliestExpenseReceiveDate` — a received date is required for the earliest expense.
   - `Rule_ValidEndReason` — the end reason code must be a value from the EAIP end-reason code table.
   - `Rule_ValidAggravationPolicy` / `Rule_ValidAggravationProvider` — the aggravation policy number must resolve, and the provider location must be valid for the other employer.
   - `Rule_HasExpenseText` — a purchase justification is required once the period has purchases in a pay status.
   - `Rule_ValidInterruptedDates` — the concurrent-injury interruption start date must be on or before the interruption end date.
   - `Rule_ValidNoFederal` and `Rule_ValidNoInmate` are warning-severity: a federal claim or an inmate claim raises a message but does not block the save.
   - `Rule_ValidHasUnPaidExpenses` (blocking a change to inactive while expenses are in `Pay`, `Pending`, `Approve`, or `New`) is present in the source but its registration line is commented out, so it never executes.
8. **Bind the parameters.** `AddInsertUpdateParms` binds the period identifier, claim identifier, specialist, claim activity date, effective and expiration dates, end reason code, status code and date, note text (4000), regular and modified job duties (200 each), purchase justification (4000), the aggravation policy or provider identifiers, the aggravation WCD identifier, the four concurrent-injury values, the earliest expense receive date, the create user and date, and the update number. Aggravation identifiers equal to zero are bound as typed nulls rather than zeros, and when the update number is still zero the update user and update date are bound as empty smart values so a never-updated row stores nulls instead of blanks.
9. **Insert.** `BeforeInsert` builds a non-query request against `Pk82500_eaip.eaip_period_insert` and forces the request type to non-query so the package's `RowsAffected` output parameter is honored ([SQL-012](../sql/SQL-012.md), [Details](../procedures/pk82500_eaip.md#eaip_period_insert)). `AfterInsert` reads `RowsAffected`.
   - If the count is not one: add `InsertError` with the message `Insert Failed - Service Period` and stop; children are not saved.
   - If the count is one: cascade to the children (step 11) and mark the object old.
10. **Update.** `BeforeUpdate` binds `LastUpdateNum` from the current update info, then replaces the update info with the acting user, the current timestamp, and the incremented update number before binding the rest of the parameters, so the statement compares the *old* number and writes the *new* one ([SQL-032](../sql/SQL-032.md), [Details](../procedures/pk82500_eaip.md#eaip_period_update), [BR-PROC-002](../business-rules.md#br-proc-002-protect-eaip-period-updates-with-optimistic-concurrency)). `AfterUpdate` applies the same `RowsAffected` check and adds `UpdateError` with the DB contention message on failure.
11. **Cascade the save.** On a successful insert or update, and only for collections that were actually materialized (a null backing field means the collection was never touched and is skipped), the object saves purchases, then wage subsidies, then period history, then user history. Period history rows (`clsPeriodHistory`) capture the status code, status date, and end reason for the period; user history rows (`clsUserHistory`) capture the specialist assigned. Both derive from `clsHistoryBase`, which reserves its identifier with a scalar `nextval` query, exposes only insert, retrieve, and delete statements, and deliberately throws an invalid-statement exception from its update statement property — history is append-only.
12. **Retrieve.** `BeforeRetrieve` binds the period identifier to `Pk82500_eaip.eaip_period_select` ([SQL-021](../sql/SQL-021.md), [Details](../procedures/pk82500_eaip.md#eaip_period_select)). `AfterRetrieve` walks the result set and, for each row, first reloads the claim data for the row's claim identifier, then fills the period fields and marks the object old. Loading claim data per row means a collection read of many periods issues one claim lookup per period.*
13. **Delete.** `BeforeDelete` binds the period identifier and the last update number to `Pk82500_eaip.eaip_period_delete` ([SQL-042](../sql/SQL-042.md), [Details](../procedures/pk82500_eaip.md#eaip_period_delete)). `AfterDelete` checks `RowsAffected` and adds `DeleteError` with the DB contention message when it is not one. Child rows are not deleted by this code: database triggers remove the dependent rows, and the package itself deletes any `EAIP_EXPENSE` rows left without a purchase or wage-subsidy parent ([BR-PROC-004](../business-rules.md#br-proc-004-clean-up-orphan-eaip-expenses-after-deleting-a-period)).
14. **Project the status list for a claim.** `GetPeriodStatusList(inDPI, inClaimID)` reads every period for the claim, projects each into a lightweight `clsPeriodStatus` carrying the status description, and orders the results by an explicit ordering sequence over the values `""`, `New`, `Active`, and `Inactive` so a claim summary shows the periods in workflow order rather than alphabetical order.

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
	Start([Caller has data portal and claim]) --> Which{New, read, or read all?}
	Which -->|New| Create[Create: eaip_period_seq nextval,<br>status New, status date today]
	Which -->|Read one| Ret[BeforeRetrieve binds period id<br>to eaip_period_select]
	Which -->|Read all| Coll[GetPeriods loads collection<br>by claim id]
	Ret --> Fill[AfterRetrieve reloads claim data<br>per row then fills fields]
	Coll --> Fill
	Create --> Edit[Caller sets specialist, dates,<br>end reason, text, aggravation]
	Fill --> Edit
	Edit --> Valid{Rules pass?}
	Valid -->|No| Broken([Broken rules returned;<br>edits retained in memory])
	Valid -->|Yes| Mode{New or existing row?}
	Mode -->|New| Ins[BeforeInsert binds parms<br>and calls eaip_period_insert]
	Mode -->|Existing| Upd[BeforeUpdate binds LastUpdateNum,<br>increments update info,<br>calls eaip_period_update]
	Ins --> Rows{RowsAffected = 1?}
	Upd --> Rows
	Rows -->|No| Err([InsertError or UpdateError<br>with DB contention message])
	Rows -->|Yes| Cascade[Save purchases, wage subsidies,<br>period history, user history]
	Cascade --> Done([Period and children persisted;<br>objects marked old])
	Edit --> Del{Delete requested?}
	Del -->|Yes| DelReq[BeforeDelete binds period id<br>and LastUpdateNum]
	DelReq --> DelRows{RowsAffected = 1?}
	DelRows -->|No| Err
	DelRows -->|Yes| Trig([Triggers remove children;<br>package clears orphan expenses])
```

## Failure and edge-case behavior

- Every persisting path checks the package's `RowsAffected` output parameter rather than a driver row count, and adds an error message instead of throwing. A stale `UpdateNum` therefore surfaces as a DB contention error on the object, and the caller must inspect the error collection ([BR-PROC-002](../business-rules.md#br-proc-002-protect-eaip-period-updates-with-optimistic-concurrency)).
- A failed insert or update short-circuits the child cascade, so purchases and wage subsidies edited in the same session remain dirty and unsaved when the parent write fails.
- Deletion depends on database triggers plus package-side orphan cleanup rather than explicit child deletes in this library; if the triggers are absent in a given environment, child rows survive the parent ([BR-PROC-004](../business-rules.md#br-proc-004-clean-up-orphan-eaip-expenses-after-deleting-a-period)).
- The overlap rule re-reads all periods for the claim on every validation pass, so validating a claim with many periods issues a package read per pass ([BR-PROC-013](../business-rules.md#br-proc-013-eaip-service-periods-for-a-claim-may-not-overlap)).
- Federal-claim and inmate-claim conditions are warnings only. A period can be created and paid on such a claim; the messages exist to prompt a manual check.
- The commented-out unpaid-expense rule means a period can be set inactive while expenses remain in `New`, `Pay`, `Pending`, or `Approve` status. The rule text survives in the source as a `RuleData` attribute, which makes the gap easy to mistake for active behavior.*
- Aggravation policy and provider location are mutually exclusive by construction, so a caller that sets both in sequence silently keeps only the last one written.
- Retrieval populates whatever the package returns; when no row matches, the object stays in its unfilled state rather than raising a not-found error.*

## Related documentation

- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [SQL catalog](../sql/index.md)
- [Database tables](../database-tables.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Sources

- `Business\SAIF.Business.Claim.EAIP\Period\clsPeriod.vb`
- `Business\SAIF.Business.Claim.EAIP\Period\clsPeriodHistory.vb`
- `Business\SAIF.Business.Claim.EAIP\Period\clsUserHistory.vb`
- `Business\SAIF.Business.Claim.EAIP\Period\clsHistoryBase.vb`
- `Business\SAIF.Business.Claim.EAIP\clsRecordInfo.vb`
- `Business\SAIF.Business.Claim.EAIP\clsUpdateInfo.vb`
- `Business\SAIF.Business.Claim.EAIP\mdlCodeTables.vb`
