---
title: Maintain provider address
summary: Persists provider address records after validating effective/expiration date overlap rules and normalizing address content through USPS standardization.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.Provider
reference_type: package
operation_id: OP-PRV-04
trigger: Caller creates or retrieves a clsProviderAddress, edits properties, and calls Save() or Delete()
---

# Maintain provider address

`clsProviderAddress` maintains dated address records (physical, mailing, etc.) per provider location. Every address type keeps its own effective/expiration timeline, addresses are normalized against USPS standards through `CleanAddressHelper`, and address changes for the current, unexpired record are synchronized to Contact Manager through `clsAddressApi`.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-PRV-04 |
| **Trigger** | Caller calls `NewProviderAddress`/`GetProviderAddress`, edits properties, and calls `Save()` or `Delete()` |
| **Inputs** | `ProviderLocationSeqNum`, `AddressTypeCode`, `EffDate`/`ExpDate`, `ForeignAddressFlag`, address lines 1–3, `CityName`, `StateCode`, `ZipCode`/`ZipCodeExt`, `CountryCode` |
| **Outputs** | Persisted [PROVIDER_ADDRESS](../database-tables.md#provider_address) row; USPS-standardized address content; Contact Manager `AddressDto` sync |
| **Primary components** | `clsProviderAddress.Create`, `BeforeInsert/BeforeUpdate/BeforeDelete`, `CleanAddressHelper.DetermineAddressLinesToClean`, `clsAddressApi.Add/Update/RemoveAddress` |
| **Entry point** | `Business\SAIF.Business.Provider\clsProviderAddress.vb` |

## Steps

1. **Create.** `Create()` assigns `ProviderLocationSeqNum` and a new `ProviderAddressSeqNum` via `GetNextSeqNum("PROVIDER_ADDRESS_SEQ")` when created from a location, or uses the criteria's address seq num directly for other construction paths.
2. **Retrieve.** `BeforeRetrieve`/`AfterRetrieve` read [PROVIDER_ADDRESS](../database-tables.md#provider_address) by `PROVIDER_ADDRESS_SEQ_NUM` ([SQL-043](../sql/SQL-043-retrieve-provider-address.md)); if the retrieved row is flagged foreign (`ForeignAddressFlag = "Y"`), `StateCode` is cleared in memory immediately after fill.
3. **Foreign/domestic toggling.** Setting `ForeignAddressFlag = "Y"` clears `StateCode` as a side effect of the property setter, before save is even attempted.
4. **Validate dates and content before save.**
   - `ValidateEffDate`: hard error if the effective date is invalid, or (when modified) is before today.
   - `ValidateExpDate`: hard error if the expiration date is today or in the future, or more than 30 years old; also blocks clearing an expiration date when a later same-type address already exists ([BR-026](../business-rules.md#br-026-a-provider-address-expiration-date-cannot-be-removed-while-a-later-address-of-the-same-type-exists)).
   - `ValidateEffDateVsExpDate`: hard error if the effective date is after the expiration date.
   - `ValidateExistingDates` / `ValidateEffOverlapingDates` / `ValidateExpOverlapingDates`: hard errors that scan `RelatedAddresses` (same address type only) and reject a new open-ended address when another open-ended row of the same type exists, or when effective/expiration ranges overlap ([BR-007](../business-rules.md#br-007-address-effective-periods-must-not-overlap)).
   - `ValidateAddress`/`ValidateCity`/`ValidateState`/`ValidateZip`/`ValidateZipExt`: hard errors requiring address line 1 always, and city/state (validated against the state code list)/ZIP (≥5 digits)/ZIP+4 (exactly 4 digits when present) only for domestic addresses (`ForeignAddressFlag = "N"`) ([BR-008](../business-rules.md#br-008-domestic-addresses-require-city-state-and-zip-data)).
5. **Normalize the address (warning-only).** `ValidateCleanAddress` calls `CleanAddressHelper`:
   - `DetermineAddressLinesToClean()` sends only two lines to the cleansing service — lines 2+3 if line 3 is populated, otherwise lines 1+2 — and calls `service.CleanAddress(line1, line2, city, state, zip, zip4, True)`.
   - If the service reports the address was cleaned: `CheckForDroppedAddressLine()` restores the original line 1 if the cleanser appears to have dropped it (detected by comparing the first three characters of the old line 2 against the old line 1), then `UpdateAddressLines()` writes the proper-cased, standardized lines/city/state/zip/zip4 back onto the object.
   - The rule raises a warning either way: `"Warning: Address has been updated to postal standards."` when cleaned, or `"Warning: Address may be incorrect."` when the cleanser could not standardize it — neither blocks the save.
6. **Insert.** `BeforeInsert` writes address type, dates, lines, city/state/zip/zip4/country, `ForeignAddressFlag`, and audit fields to [PROVIDER_ADDRESS](../database-tables.md#provider_address) ([SQL-019](../sql/SQL-019-insert-provider-address.md)).
   - `FillAdressDto()` builds `AddressDto` with resolved `addressType` description, `providerNumber`, and `isPrimary` (via `clsProviderUtility`).
   - On success, `AddContactManagerAddress()` calls the API **only if** `ExpDate = Date.MinValue` (the address is current/effective); an expired address on creation is not synced.*
7. **Update.** `BeforeUpdate` writes the same fields to [PROVIDER_ADDRESS](../database-tables.md#provider_address) ([SQL-067](../sql/SQL-067-update-provider-address.md)) with concurrency check `WHERE PROVIDER_ADDRESS_SEQ_NUM = :ProviderAddressSeqNum AND UPDATE_NUM = :UpdateNum`.
   - `UpdateContactManagerAddress()` branches on the transition between the originally retrieved expiration date and the new one:
     - Was open-ended, now expired → calls `RemoveContactManagerAddressApi()`.
     - Was open-ended, still open-ended → calls `UpdateContactManagerAddressApi()`.
     - Was expired, now open-ended → calls `AddContactManagerAddressApi()`.
     - Was expired, still expired → no API call.
   - `clsAddressApi` routes: insert/update `provider/{providerLocationSeqNumber}/address`, delete `provider/{providerLocationSeqNumber}/address/remove`; exceptions are caught by `CreateErrorMessage()`, which logs the serialized DTO and adds an `InsertError`/`UpdateError`/`DeleteError`.
8. **Delete.** `BeforeDelete` deletes the [PROVIDER_ADDRESS](../database-tables.md#provider_address) row by `ProviderAddressSeqNum` ([SQL-089](../sql/SQL-089-delete-provider-address.md)); `AfterDelete` only checks rows affected and marks the object new — **no Contact Manager remove call is made on delete**, unlike the expiration-transition sync used during update.*

## Process flow

```mermaid
flowchart TD
	Start([Caller edits address]) --> Domestic{Foreign or domestic?}
	Domestic -->|Domestic| ReqFields[Require line1, city, state, zip]
	Domestic -->|Foreign| SkipFields[Skip city/state/zip requirement;<br>state forced blank]
	ReqFields --> Overlap{Overlaps or duplicates<br>same-type open-ended address?}
	SkipFields --> Overlap
	Overlap -->|Yes| Fail([Overlapping effective period<br>blocks save])
	Overlap -->|No| Clean[Send 2 lines to CleanAddressHelper]
	Clean --> Cleaned{Standardized?}
	Cleaned -->|Yes| WarnClean([Warning: updated to postal standards])
	Cleaned -->|No| WarnBad([Warning: address may be incorrect])
	WarnClean --> Persist[Insert or update PROVIDER_ADDRESS]
	WarnBad --> Persist
	Persist --> Rows{RowsAffected = 1?}
	Rows -->|No| Contention([Insert Failed / DB contention error])
	Rows -->|Yes| ExpTransition{Expiration transition?}
	ExpTransition -->|Open to expired| ApiRemove[Contact Manager remove]
	ExpTransition -->|Expired to open| ApiAdd[Contact Manager add]
	ExpTransition -->|No material change| NoApi([No Contact Manager call])
	ApiRemove --> Done([Save complete])
	ApiAdd --> Done
	NoApi --> Done
```

## Failure and edge-case behavior

- Deleting an address never issues a Contact Manager removal call, so a deleted current address can remain visible in Contact Manager until a subsequent update elsewhere triggers a sync.*
- USPS standardization always raises a warning (success or failure) but never blocks the save — an unrecognized address can still be persisted.
- Overlap and duplicate-open-ended checks are scoped to addresses of the **same type only**; different address types on the same location do not interact.
- Insert-time Contact Manager sync is skipped entirely for a brand-new address that is already expired.*

## 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.Provider\clsProviderAddress.vb`
- `Business\SAIF.Business.Provider\CleanAddressHelper.vb`
- `Business\SAIF.Business.Provider\Api\clsAddressApi.vb`
