---
title: Claim detail CRUD
summary: Retrieves the core claim header, validates field-level edits, and persists the header together with its dependent child collections.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.Maintenance
reference_type: package
operation_id: OP-CM-01
trigger: Application or UI layer calls clsClaimDetail.GetClaimDetail(dpi, claimID) and later Save()
---

# Claim detail CRUD

Claim detail CRUD is the central maintenance operation for a claim header. It loads the `CL_INJURY_CLAIM` row for a claim, enriches it with denormalized adjuster names and condition counts, exposes every mutable header field for editing, runs roughly twenty field-level validation rules, cascades saves into the dependent child collections (MCO eligibility, status/type/disability activity, request decisions, pharmacy eligibility, policy history, COVID-19 testing history), derives dependent fields, and finally updates the header under optimistic concurrency. The business object supports retrieve and update only — creation and deletion of claim rows happen elsewhere.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CM-01 |
| **Trigger** | Caller invokes `clsClaimDetail.GetClaimDetail(dpi, claimID)` to load a claim, edits properties, and calls `Save()`; also loaded as a nested object by other claim-maintenance business objects |
| **Inputs** | `ClaimID` (retrieve key via `Criteria`); mutable header fields (`ActiveClaimFlag`, `ExternalViewFlag`, `ClaimStatusCode`, `ClaimTypeCode`, `CoverageCode`, payroll class/sub codes, `InjOccupationalDiseaseCode`, `InmateNum`, `JurisdictionCode`, MCO fields, `OwcpClaimNum`, `OwcpServiceOfficeCode`, `Federal8FFlag`, policy fields, `ClaimRequestTypeCode`, `ClaimRespCode`, `CompensabilityRespCode`, `DisabilityDate`, `KnowledgeDate`, `WCDClaimNum`, `MedicareORMTermDate`, `TestedForCovid19Flag`, `Covid19TestResultCode`); `UpdateNum`; `IDataPortalInfo` |
| **Outputs** | Populated `clsClaimDetail` instance on retrieve; boolean success plus mutated child objects and refreshed `UpdateNum`/`UpdateDate`/`UpdateUser` on save; validation error and warning messages |
| **Primary components** | `clsClaimDetail.GetClaimDetail`, `clsClaimDetail.BeforeRetrieve`, `clsClaimDetail.AfterRetrieve`, `clsClaimDetail.Fill`, `clsClaimDetail.IsValid`, `clsClaimDetail.AddBusinessRules`, `clsClaimDetail.Save`, `clsClaimDetail.BeforeUpdate`, `clsClaimDetail.AfterUpdate`, `clsClaimDetail.ProcessJurisdictionCodeChanges`, `clsClaimDetail.ProcessLegalRep` |
| **Entry point** | `Business\SAIF.Business.Claim.Maintenance\clsClaimDetail.vb` |

## Steps

1. **Dispatch the retrieve.** `GetClaimDetail(inDataportal, inClaimID)` builds a `Criteria(inDataportal, inClaimID)` and calls `DataAccessLayer.RetrieveObject`, which the BOF data portal routes to `BeforeRetrieve` and `AfterRetrieve`.
2. **Build the primary select.** `BeforeRetrieve` assigns `DataRequest.SQLStatement = MyCriteria.RetrieveSQL`, names the request `ClaimDetailRetrieve`, and binds the `ClaimID` parameter ([SQL-040](../sql/SQL-040.md), [CL_INJURY_CLAIM](../database-tables.md#cl_injury_claim), joined to `CL_FORM_CODE` for the form-code description).
3. **Read the row and hydrate the object.** `AfterRetrieve` reads the first row and calls `Fill(Reader)`.
   - If the reader returns no row: `Fill` is skipped, only `MarkOld()` runs, and the caller receives an object with default/empty field values rather than an explicit not-found error.
4. **Map the reader onto member fields (`Fill`).** Roughly thirty-five columns are mapped, including claim status/type, coverage, payroll class codes, form code and description, MCO fields, OWCP fields, policy fields, responsibility codes, `InjuryDate`, `DisabilityDate`, `KnowledgeDate`, `MedicareORMTermDate`, claim score, and the COVID-19 test fields ([SQL-040](../sql/SQL-040.md) result set; no further database access).
5. **Resolve the responsible-adjuster display name.** If `ClaimRespCode` is non-blank, `GetAdjusterName(ClaimRespCode)` delegates to `clsClaimResponsibilityData.GetData(...)`; when the returned data is valid, `RespAdjName` prefers the adjuster's preferred name over the first name and otherwise falls back to the description. A blank `ClaimRespCode` clears `RespAdjName`.
   - Sub-step: the responsibility-data object performs its own retrieve of the claim-responsibility reference row and exposes the adjuster name fields consumed here.

   > **Origin**: The claim-responsibility lookup is implemented in `clsClaimResponsibilityData` (`SAIF.Business.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-claim/operations-processes.md) in the `SAIF.Business.Claim` documentation set.
6. **Resolve the compensability-adjuster display name.** The same lookup pattern runs against `CompensabilityRespCode` and populates `CompAdjName`.
7. **Look up the merged claim for voided claims.** If `ClaimStatusCode` is `V` (voided), `GetMergedClaim()` issues a scalar query joining the merged-claim table to the claim table to populate `MergedClaimNum` ([SQL-037](../sql/SQL-037.md), [CL_MERGED_CLAIM](../database-tables.md#cl_merged_claim), [CL_INJURY_CLAIM](../database-tables.md#cl_injury_claim)).
8. **Compute condition counts used later by validation.** `GetAcceptCondMedStationaryCount()` counts accepted conditions with no medically-stationary date ([SQL-032](../sql/SQL-032.md), [CL_MEDICAL_CONDITION](../database-tables.md#cl_medical_condition), [CL_COND_COMPENSABILITY_DECISN](../database-tables.md#cl_cond_compensability_decisn)) and `GetConditionsCount()` counts all conditions for the claim ([SQL-034](../sql/SQL-034.md), [CL_MEDICAL_CONDITION](../database-tables.md#cl_medical_condition)). `WCDClaimCount` and `OWCPClaimCount` are initialized to `-1` as a not-yet-checked sentinel. These counts feed [BR-CM-020](../business-rules.md#br-cm-020-claim-status-may-not-change-without-at-least-one-condition) and [BR-CM-027](../business-rules.md#br-cm-027-closing-a-claim-without-a-medically-stationary-date-warns-the-user).
9. **Finalize the retrieve.** `MarkOld()` clears the object's modified flags so later edits are detectable through `ValueModified`.
10. **Lazy-load related collections on first access.** `ClaimMcoEligibility`, `ClaimTypeActivity`, `ClaimStatusActivity`, `ClaimDisabilityActivity`, `ClaimRequestDecision`, `DisabilityAuthActivity`, `Covid19TestingHistoryCollection`, and `WorkerPharmacyEligibilityReport` each retrieve their own child object the first time they are read. `McoPlanID` is derived by `GetMCOPlanID` ([SQL-036](../sql/SQL-036.md), [MCO_GSA_LOCATION](../database-tables.md#mco_gsa_location)) and cached until `McoID` is reassigned.
11. **Caller edits properties.** Every mutable setter compares the old and new value and calls `MarkDirty()` only on a real change. Setting `JurisdictionCode`, `ClaimStatusCode`, `ClaimTypeCode`, `DisabilityDate`, or `KnowledgeDate` also latches a "modified" tracking flag consumed by `IsValid`. Setting `WCDClaimNum` stamps `WCDClaimNumUpdateUser`/`WCDClaimNumUpdateDate`; setting `McoID` clears the cached `McoPlanID`.
12. **Run `IsValid()` before every save.**
    - Backfill `ClaimTypeCode`, `ClaimStatusCode`, and `DisabilityDate` from the latest child-activity rows when the corresponding save sentinel is still empty, so values driven by child-collection saves earlier in the same `Save()` call are picked up.
    - If the status changed to Closed or Denied and the pharmacy-eligibility record is not already Ineligible, force the pharmacy status to `I` with today's date ([BR-CM-029](../business-rules.md#br-cm-029-closing-or-denying-a-claim-sets-pharmacy-eligibility-to-ineligible)).
    - If the status changed to Denied, set `MedicareORMTermDate` from the claim-status-activity effective date; if it changed to Open, clear it ([BR-CM-030](../business-rules.md#br-cm-030-denying-a-claim-sets-the-medicare-orm-termination-date-and-reopening-clears-it)).
    - Call `MyBase.IsValid`, then re-register child rule propagation (`ValidateChildObject`) for the MCO eligibility, status/type activity, request decision, disability activity, disability authorization, and pharmacy eligibility children, because the base call clears child rules.
    - Refresh the read-only `JurisdictionModified`, `ClaimStatusCodeModified`, `ClaimTypeCodeModified`, `ClaimTypeChangedToDisabling`, `DisabilityDateModified`, and `KnowledgeDateModified` flags.
    - Return `ErrorCount = 0`; the boolean computed by `MyBase.IsValid` is discarded, so the effective gate is the error count.
13. **Evaluate the registered field-level rules.** `AddBusinessRules()` wires roughly twenty rules that run whenever their bound property is set and again during `IsValid()`:
    - Code and flag domains: [BR-CM-009](../business-rules.md#br-cm-009-injury-occupational-disease-code-must-be-i-or-d), [BR-CM-010](../business-rules.md#br-cm-010-active-claim-flag-must-be-y-or-n), [BR-CM-013](../business-rules.md#br-cm-013-coverage-code-must-be-a-b-c-or-e), [BR-CM-014](../business-rules.md#br-cm-014-handicap-worker-reserve-flag-must-be-y-or-n), [BR-CM-016](../business-rules.md#br-cm-016-claim-status-code-must-be-new-open-closed-or-denied).
    - Date constraints: [BR-CM-011](../business-rules.md#br-cm-011-knowledge-date-is-required-and-bounded-by-injury-date-and-today), [BR-CM-012](../business-rules.md#br-cm-012-disability-date-must-not-precede-injury-date-or-be-in-the-future), [BR-CM-017](../business-rules.md#br-cm-017-state-occupational-disease-ppd-or-ptd-claims-require-a-disability-or-initial-treatment-date), [BR-CM-022](../business-rules.md#br-cm-022-medicare-orm-termination-date-must-fall-between-injury-date-and-seven-days-ahead).
    - Uniqueness checks that issue live scalar queries: [BR-CM-015](../business-rules.md#br-cm-015-wcd-claim-number-must-be-unique-across-claims) ([SQL-041](../sql/SQL-041.md), [CL_INJURY_CLAIM](../database-tables.md#cl_injury_claim)) and [BR-CM-019](../business-rules.md#br-cm-019-owcp-claim-number-must-be-unique-per-worker) ([SQL-038](../sql/SQL-038.md), [CL_INJURY_CLAIM](../database-tables.md#cl_injury_claim)).
    - Status and decision consistency: [BR-CM-018](../business-rules.md#br-cm-018-aggravation-decision-on-a-disabling-claim-requires-a-closed-status), [BR-CM-020](../business-rules.md#br-cm-020-claim-status-may-not-change-without-at-least-one-condition), [BR-CM-021](../business-rules.md#br-cm-021-claim-status-may-not-close-while-the-latest-decision-is-deferred).
    - Warnings that do not block the save: [BR-CM-023](../business-rules.md#br-cm-023-disabling-occupational-disease-claims-should-have-a-disability-date), [BR-CM-024](../business-rules.md#br-cm-024-disability-date-changes-require-wage-re-verification), [BR-CM-025](../business-rules.md#br-cm-025-aggravation-request-after-aggravation-rights-end-date-warns-of-own-motion), [BR-CM-026](../business-rules.md#br-cm-026-non-disabling-to-disabling-change-within-365-days-warns-the-user), [BR-CM-027](../business-rules.md#br-cm-027-closing-a-claim-without-a-medically-stationary-date-warns-the-user), [BR-CM-028](../business-rules.md#br-cm-028-aggravation-decision-change-warns-of-a-possible-aggravation-decision), [BR-CM-031](../business-rules.md#br-cm-031-reopening-a-claim-with-a-cda-order-warns-the-user).
    - Pharmacy eligibility consistency: [BR-CM-032](../business-rules.md#br-cm-032-ohs-or-suspended-pharmacy-status-requires-majoris-enrollment) and [BR-CM-033](../business-rules.md#br-cm-033-caremark-or-denied-pharmacy-status-requires-caremark-enrollment).
    - If any error-severity rule breaks: the error count is non-zero and the save is refused; warnings are surfaced but allow the save to continue.
    - Dead code: `DisabAuthActivityDate_InjuryDate` (`clsClaimDetail106`) carries rule metadata but is never registered, so it never executes.
14. **Orchestrate the save (`Save()`).**
    - If `ClaimMcoEligibility` is dirty: copy `McoID`, `McoEligibilityReasonCode`, and `McoEligibilityStatusCode` onto the header, save the child collection, and derive the pharmacy formulary code from the MCO plan and enrollment status ([BR-CM-034](../business-rules.md#br-cm-034-formal-mco-enrollment-sets-the-matching-pharmacy-formulary-code)).
    - If `ClaimStatusActivity` is dirty: save the collection and re-derive `ClaimStatusCode` from `GetMaxClaimStatusCode` into the save sentinel consumed by step 12.
    - If `ClaimTypeActivity` is dirty: save the collection and re-derive `ClaimTypeCode` from `GetMaxClaimTypeCode`.
    - If `ClaimRequestDecision` is dirty: save the collection and re-derive `ClaimRequestTypeCode` from `GetMaxClaimRequestTypeCode`.
    - If `ClaimDisabilityActivity` is dirty: save the collection and re-derive `DisabilityDate` from `GetMaxDisabilityDate`.
    - If `DisabilityAuthActivity` is dirty: save the collection; no header field is re-derived.
    - If the latest or pending decision is an accept decision and the guard conditions hold: derive the pharmacy eligibility status from the MCO plan ([BR-CM-035](../business-rules.md#br-cm-035-accepted-claim-decision-derives-pharmacy-eligibility-from-mco-plan)); otherwise, if the status moved from Closed to Open, apply the reopen-specific pharmacy status ([BR-CM-036](../business-rules.md#br-cm-036-reopening-a-closed-claim-resets-pharmacy-eligibility-status)).
    - If the pharmacy eligibility record ended up dirty from any branch, save it.
    - If `JurisdictionCode` was modified, run the jurisdiction cascade (step 15) ([BR-CM-038](../business-rules.md#br-cm-038-jurisdiction-code-transitions-cascade-related-identifier-fields)).
    - If `ActiveClaimFlag` changed from `Y` to `N`, run the legal-representation termination (step 16) ([BR-CM-039](../business-rules.md#br-cm-039-deactivating-a-claim-auto-terminates-open-legal-representation)).
    - If this is the first decision transitioning to Accept or Denied, copy `ClaimRespCode` into `CompensabilityRespCode` ([BR-CM-037](../business-rules.md#br-cm-037-first-accept-or-deny-decision-copies-responsible-adjuster-to-compensability-adjuster)).
    - If a claim-policy-history child object exists, save it.
    - If `Covid19TestResultCode` or `TestedForCovid19Flag` changed, create a new testing-history row, copy the current values onto it, add it to the collection, and save the collection (append-only history).
    - Call `AcceptChanges()`, then `MyBase.Save()` (which runs steps 12, 17, and 18), mark the object old on success, and reset the save sentinels for the next cycle.
15. **Cascade jurisdiction-code transitions (`ProcessJurisdictionCodeChanges`).** Using the in-memory jurisdiction map, leaving Inmate (`2`) zeroes `InmateNum`; entering Inmate forces `PolicyNum = 8` with a blank sub-code and creates a new claim-policy-history row; leaving State (`1`) clears `WCDClaimNum`; leaving Federal clears `OwcpClaimNum` and resets `OwcpServiceOfficeCode` to `0` ([BR-CM-038](../business-rules.md#br-cm-038-jurisdiction-code-transitions-cascade-related-identifier-fields)).
16. **Auto-terminate open legal representation (`ProcessLegalRep`).** A scalar query retrieves the current open legal-representative effective date ([SQL-035](../sql/SQL-035.md), [CL_CLAIM_LEGAL_REPRESENTATIVE](../database-tables.md#cl_claim_legal_representative)); if a row exists, an update sets `EXP_DATE = TRUNC(SYSDATE)` and the end-reason code to `1` for that claim and effective date ([SQL-086](../sql/SQL-086.md), [CL_CLAIM_LEGAL_REPRESENTATIVE](../database-tables.md#cl_claim_legal_representative)) ([BR-CM-039](../business-rules.md#br-cm-039-deactivating-a-claim-auto-terminates-open-legal-representation)).
    - No row count is checked on this secondary update, so a concurrent change silently no-ops.
17. **Build the header update (`BeforeUpdate`).** The request is set to `MyCriteria.UpdateSQL` as a non-query named `ClaimDetailUpdate`, `m_UpdateUser` is stamped from the data-portal user, and roughly twenty-eight parameters are bound, including the `ClaimID`/`UpdateNum` pair used in the `WHERE` clause for optimistic concurrency ([SQL-087](../sql/SQL-087.md), [CL_INJURY_CLAIM](../database-tables.md#cl_injury_claim)) ([BR-CM-001](../business-rules.md#br-cm-001-optimistic-concurrency-updates-fail-if-rowsaffected-1)).
18. **Verify the update (`AfterUpdate`).** The non-query result is cast to an integer row count.
    - If the row count is not exactly 1: `AddErrorMessage("UpdateError", DBContentionError)` reports that another user updated the row first or that the row no longer exists ([BR-CM-001](../business-rules.md#br-cm-001-optimistic-concurrency-updates-fail-if-rowsaffected-1)).
    - Otherwise: any prior update error is cleared.
19. **Aggregate dirtiness (`IsDirty`).** The header combines its own dirty flag with those of the MCO eligibility, status/type activity, disability activity, request decision, disability authorization, pharmacy eligibility, and COVID-19 testing history children, so the caller knows whether a save is required.
20. **Serve supplementary read-only lookups.** `GetPMAWorkerNumberCount` ([SQL-039](../sql/SQL-039.md), [CL_CLAIM_PERSON](../database-tables.md#cl_claim_person)), `GetClaimBenefitPeriodCount` ([SQL-033](../sql/SQL-033.md), [CL_BENEFIT_PERIOD](../database-tables.md#cl_benefit_period), which appends an equality or inequality predicate on the payment-category code), and `GetMCOPlanID` ([SQL-036](../sql/SQL-036.md), [MCO_GSA_LOCATION](../database-tables.md#mco_gsa_location)) feed the UI and validation without participating in the retrieve/update path.

No create or delete path exists: the nested `Criteria` class throws `InvalidSQLProperty` for `CreateSQL`, `InsertSQL`, and `DeleteSQL`.

## Process flow

The following diagram visualizes this operation's control flow from retrieve through validated save.

```mermaid
flowchart TD
	Start([GetClaimDetail dpi claimID]) --> Sel[BeforeRetrieve builds select<br>and binds ClaimID]
	Sel --> Row{Row returned?}
	Row -->|No| Empty([Object marked old<br>and returned unfilled])
	Row -->|Yes| Fill[Fill maps all columns]
	Fill --> Resp{ClaimRespCode set?}
	Resp -->|Yes| RespName[Resolve responsible<br>adjuster name]
	Resp -->|No| Comp{CompensabilityRespCode set?}
	RespName --> Comp
	Comp -->|Yes| CompName[Resolve compensability<br>adjuster name]
	Comp -->|No| Void{Status = Voided?}
	CompName --> Void
	Void -->|Yes| Merged[Look up merged claim number]
	Void -->|No| Counts[Compute condition counts]
	Merged --> Counts
	Counts --> Old[MarkOld and return object]
	Old --> Edit[Caller edits properties]
	Edit --> Save[Save orchestrates child saves<br>and field derivations]
	Save --> Juris{Jurisdiction modified?}
	Juris -->|Yes| Cascade[Cascade identifier defaults]
	Juris -->|No| Active{ActiveClaimFlag Y to N?}
	Cascade --> Active
	Active -->|Yes| LegalRep[Terminate open legal rep]
	Active -->|No| Valid{IsValid: error count = 0?}
	LegalRep --> Valid
	Valid -->|No| Blocked([Broken rules returned;<br>no update issued])
	Valid -->|Yes| Update[BeforeUpdate issues header update]
	Update --> Rows{RowsAffected = 1?}
	Rows -->|Yes| Ok([Errors cleared; object marked old])
	Rows -->|No| Contention([DB contention error added;<br>save reported as failed])
```

## Failure and edge-case behavior

- **Optimistic concurrency:** `AfterUpdate` rejects the save with a DB contention error whenever the row count is not exactly 1, covering both a stale `UpdateNum` and a concurrently deleted row ([BR-CM-001](../business-rules.md#br-cm-001-optimistic-concurrency-updates-fail-if-rowsaffected-1)).
- **Silent empty retrieve:** when the primary select returns no rows, `AfterRetrieve` skips `Fill` and returns an object with default values; a non-null `clsClaimDetail` does not imply a valid claim.
- **Secondary legal-rep update has no contention check:** the legal-representative update does not verify its row count, unlike the main claim update, so a concurrent modification silently no-ops.
- **Dead and broken rule:** `DisabAuthActivityDate_InjuryDate` is never registered, and its logic sets the result to false unconditionally whenever a disability-authorization activity date is present, so re-enabling it would always fail validation.
- **Duplicate internal rule-data IDs:** `ClaimStatusCodeValid` and `PharmacyEligibilityStatusCodeValid` share the source rule ID `clsClaimDetail08`, and `DisabilityTreatmentDateValid` and `PharmacyEligibilityCaremarkCodeValid` share `clsClaimDetail09` — a cross-referencing ambiguity rather than a functional defect.
- **Uniqueness rules issue live queries as a validation side effect:** the WCD and OWCP uniqueness checks run a count query on every property set and every `IsValid()` pass while the field is non-blank, so repeated edits cause repeated round trips.
- **Pharmacy-eligibility branch ordering is significant:** the MCO-driven, accept-decision, and reopen branches are mutually exclusive and keyed on overlapping conditions; an already-dirty pharmacy record is respected so later branches do not overwrite a pending change, but reordering the conditions would change behavior.
- **Read-only supporting fields:** `InjuryDate`, `InitialTreatmentDate`, and several other fields are populated only by `Fill` and cannot be corrected through this object even though validation rules compare against them.
- **Cached MCO plan:** `McoPlanID` is cached after first computation and invalidated only when `McoID` is re-set, so an external change to the plan mapping mid-session leaves a stale value.

## Related documentation

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

## Sources

- `Business\SAIF.Business.Claim.Maintenance\clsClaimDetail.vb`
