---
title: Tax ID history management
summary: Maintains per-person tax ID history rows, validates primary and duplicate-tax-ID rules, and persists history-row inserts, updates, and deletes.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.Person
reference_type: package
operation_id: OP-PER-04
trigger: Caller retrieves a person's tax ID history collection, adds or edits history rows, and saves the collection.
---

# Tax ID history management

Tax ID history management loads all history rows for one claim person, lets the caller add or edit individual rows with collection-level primary-selection checks, and persists child-row inserts, updates, and deletes against the person tax ID history table. The operation also runs duplicate and primary-tax-ID validation queries before save.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-PER-04 |
| **Trigger** | Caller retrieves tax ID history for a person, changes one or more rows, and calls `Save()` on the collection |
| **Inputs** | `PersonID`; optional `PrimaryFlag` for a new row; row fields `TaxID`, `PrimaryFlag`, `VoidFlag`; acting user from `DataPortalInfo.UserID` |
| **Outputs** | In-memory collection of tax ID history rows; persisted inserts, updates, or deletes in `CL_PERSON_TAX_ID_HISTORY`; broken rules, warnings, and DB-contention messages |
| **Primary components** | `clsPersonTaxIDHistoryCollection.GetPersonTaxIDHistoryCollection`, `clsPersonTaxIDHistoryCollection.NewPersonTaxIDHistory`, `clsPersonTaxIDHistory.BeforeInsert`, `clsPersonTaxIDHistory.BeforeUpdate`, `clsPersonTaxIDHistory.BeforeDelete` |
| **Entry point** | `Business\SAIF.Business.Claim.Person\clsPersonTaxIDHistoryCollection.vb` |

## Steps

1. **Retrieve the person's full tax ID history.** `GetPersonTaxIDHistoryCollection` issues the collection read query, loads each row into `clsPersonTaxIDHistory`, and tracks the highest `PRIMARY_KEY_SEQ_NUM` for later child creation ([SQL-013](../sql/sql-013-select.md), [CL_PERSON_TAX_ID_HISTORY](../database-tables.md#cl_person_tax_id_history)).
2. **Create a new history row when requested.** `NewPersonTaxIDHistory` increments the cached maximum sequence number, creates a child row for the current `PersonID`, copies the optional primary flag into the child criteria, and initializes the new row with a blank tax ID and `VoidFlag = "N"`.
3. **Validate tax ID format and flag values on each dirty row.** `clsPersonTaxIDHistory.AddBusinessRules` enforces a nine-digit tax ID, a `PrimaryFlag` of `Y` or `N`, and a `VoidFlag` of `Y` or `N` ([BR-PER-003](../business-rules.md#br-per-003-tax-id-must-contain-exactly-nine-digits), [BR-PER-004](../business-rules.md#br-per-004-primary-flag-must-be-y-or-n), [BR-PER-005](../business-rules.md#br-per-005-void-flag-must-be-y-or-n)).
4. **Validate primary and duplicate-tax-ID behavior.**
   - If the row is marked primary and the tax ID has nine digits, query for another worker-person row with the same tax ID and a primary flag; any match blocks save ([BR-PER-006](../business-rules.md#br-per-006-primary-tax-ids-must-be-unique-across-worker-records), [SQL-011](../sql/sql-011-select.md), [CL_PERSON_TAX_ID_HISTORY](../database-tables.md#cl_person_tax_id_history), [CL_CLAIM_PERSON](../database-tables.md#cl_claim_person)).
   - If the tax ID has nine digits, query for any other person using the same value; primary rows check only active non-primary rows, while non-primary rows check all other rows. A match adds a warning rather than a blocking error ([BR-PER-007](../business-rules.md#br-per-007-duplicate-tax-ids-raise-a-warning), [SQL-010](../sql/sql-010-select.md), [SQL-012](../sql/sql-012-select.md), [CL_PERSON_TAX_ID_HISTORY](../database-tables.md#cl_person_tax_id_history)).
   - If a row is both primary and void, validation fails ([BR-PER-009](../business-rules.md#br-per-009-primary-tax-ids-cannot-be-voided)).
5. **Validate the collection-wide primary selection.** Before save, the collection-level rule counts primary rows across the managed list and rejects the change when more than one row is primary ([BR-PER-008](../business-rules.md#br-per-008-only-one-tax-id-can-be-primary-per-person)).
6. **Persist each child-row insert, update, or delete through BOF save processing.** `clsPersonTaxIDHistoryCollection.Save` calls `ApplyEdit()` and then delegates to the managed child objects, which execute the matching row-level SQL for new, edited, and deleted items ([BR-PER-001](../business-rules.md#br-per-001-optimistic-concurrency-applies-to-crud-operations), [SQL-003](../sql/sql-003-insert.md), [SQL-019](../sql/sql-019-update.md), [SQL-022](../sql/sql-022-delete.md), [CL_PERSON_TAX_ID_HISTORY](../database-tables.md#cl_person_tax_id_history)).
7. **Translate rows-affected mismatches into contention errors.** After each insert, update, or delete, the child object checks the returned row count and adds the standard DB-contention message when the operation does not affect exactly one row ([BR-PER-001](../business-rules.md#br-per-001-optimistic-concurrency-applies-to-crud-operations)).

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
    Start([Load tax ID history for one person]) --> Read[Retrieve history rows]
    Read --> Change{Add, edit, or delete a row?}
    Change -->|Add| NewRow[Assign next sequence<br>and initialize defaults]
    Change -->|Edit| Validate[Validate row and collection]
    Change -->|Delete| DeleteRow[Queue child delete]
    NewRow --> Validate
    Validate --> Valid{Validation passes?}
    Valid -->|No| Fail([Broken rules or warnings returned])
    Valid -->|Yes| Save[Run managed child save]
    DeleteRow --> Save
    Save --> Rows{Rows affected = 1?}
    Rows -->|No| Contention([Add DB-contention message])
    Rows -->|Yes| Done([Save completes])
```

## Failure and edge-case behavior

- Duplicate-tax-ID checks are asymmetric by design: primary rows query active non-primary duplicates, while non-primary rows query all other rows.
- Duplicate matches from `SSNInUse` are warnings, so they do not block save by themselves.
- The collection-level single-primary rule only runs when the collection is dirty, so untouched collections are not revalidated.
- A primary tax ID cannot be voided, even if every other field is valid.
- The class exposes `UpdateClaimPersonSQL`, but this operation does not call it anywhere in the tax-ID-history save path.

## 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.Person\clsPersonTaxIDHistory.vb`
- `Business\SAIF.Business.Claim.Person\clsPersonTaxIDHistoryCollection.vb`
