---
title: Maintain provider location
summary: Creates, updates, and deletes a provider's physical practice location/registration record and synchronizes core identifying fields to Contact Manager.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.Provider
reference_type: package
operation_id: OP-PRV-03
trigger: Caller creates or retrieves a clsProviderLocation, edits properties, and calls Save() or Delete()
---

# Maintain provider location

`clsProviderLocation` is the anchor record for a provider's practice location: it stores the business/individual identity, contact numbers, approval and eligibility flags, and its `ProviderLocationSeqNum` is the foreign key used by nearly every other class in this library. Every successful save or delete also pushes a `ContactDto` to Contact Manager through `clsContactApi`.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-PRV-03 |
| **Trigger** | Caller calls `NewProviderLocation`/`GetProviderLocation`, edits properties, and calls `Save()` or `Delete()` |
| **Inputs** | `ProviderSeqNum`, `ProviderTypeCode`, `BusinessFlag`, name fields, phone/fax/toll-free numbers, `WebSiteAddress`, `PayeeName`, `EmailAddr`, approval/primary/fraud/ineligibility/activity flags |
| **Outputs** | Persisted [PROVIDER_LOCATION](../database-tables.md#provider_location) row; Contact Manager `ContactDto` sync |
| **Primary components** | `clsProviderLocation.Create`, `BeforeInsert/BeforeUpdate/BeforeDelete`, `FillContactDto`, `clsContactApi.AddContact/UpdateContact/RemoveContact` |
| **Entry point** | `Business\SAIF.Business.Provider\clsProviderLocation.vb` |

## Steps

1. **Create a new location.** `Create()` sets `ProviderSeqNum` from the criteria, assigns a new `ProviderLocationSeqNum` via `GetNextSeqNum("PROVIDER_LOCATION_SEQ")`, defaults `ApprovedUser = "SYSTEM"`, and stamps the acting user.
2. **Name normalization on entity-type change.** Setting `BusinessFlag = "Y"` clears `FirstName`/`MiddleName`/`LastName`; for `BusinessFlag = "N"`, editing any of `FirstName`/`MiddleName`/`LastName` rebuilds `BusinessName` as `"First Middle Last"` with collapsed double spaces; the `BusinessName` and `LastName` setters also maintain uppercase shadow columns used for search.
3. **Validate before save.**
   - [BR-003](../business-rules.md#br-003-only-one-primary-location-is-allowed): `HasDuplicatePrimaryLocation` blocks a save when `PrimaryLocationFlag = "Y"` and another primary location already exists for the same provider.
   - [BR-004](../business-rules.md#br-004-provider-type-is-required-for-a-location): `MissingProviderType` requires `ProviderTypeCode`.
   - [BR-005](../business-rules.md#br-005-business-locations-require-a-business-name): `MissingBusinessName` requires `BusinessName` when `BusinessFlag = "Y"`.
   - [BR-006](../business-rules.md#br-006-individual-locations-require-a-last-name): `MissingLastName` requires `LastName` when `BusinessFlag = "N"`.
   - `ValidPhoneNumber`, `ValidTollFreePhoneNumber`, `ValidFax`, `ValidTollFreeFax`: hard errors requiring at least 12 formatted characters when a value is present.
   - `CommonBusinessRules.IsValidEmailAddress` validates `EmailAddr` when supplied.
4. **Insert.** `BeforeInsert` writes provider type, business/individual identity, approval/primary/fraud/ineligibility/activity flags, phone/fax/toll-free numbers, `WebSiteAddress`, `PayeeName`, `EmailAddr`, and audit fields into [PROVIDER_LOCATION](../database-tables.md#provider_location) ([SQL-028](../sql/SQL-028-insert-provider-location.md)).
   - `FillContactDto()` builds `ContactDto` with `companyName`/`firstName`/`lastName`/`isCompanyType`, `primaryPhone` (digits only), `fax`, `providerlocationSeqNumber`, `specialityType` (via `clsProviderUtility.GetSpecialityDescriptionByCode`), `providerNumber`, and `tin` (via `clsProviderUtility.GetTaxIdByProviderSeqNum`).
   - On a rows-affected value other than `1`: add `"Insert Failed"`.
   - On success: `MarkOld()`, then call `clsContactApi.AddContact(...)`.
     - `clsContactApi.AddContact` sends an HTTP insert to Provider API route `provider/{providerlocationSeqNumber}` with the `ContactDto` body.
     - On an API exception: serialize the DTO to JSON, log it via `Logging.clsLogError.CreateError(...)`, and add an `ApiInsertError` — the database row remains committed; there is no compensating rollback.*
5. **Update.** `BeforeUpdate` writes the same field set to [PROVIDER_LOCATION](../database-tables.md#provider_location) ([SQL-076](../sql/SQL-076-update-provider-location.md)) with concurrency check `WHERE PROVIDER_LOCATION_SEQ_NUM = :ProviderLocationSeqNum AND UPDATE_NUM = :UpdateNum`.
   - On a rows-affected value other than `1`: add the DB contention error.
   - On success: increment `m_ProviderLocationSeqNum` locally, `MarkOld()`, then call `clsContactApi.UpdateContact(...)` (route `provider/{providerlocationSeqNumber}`, same DTO); API failures are logged and surfaced as `ApiUpdateError` without blocking the already-committed DB update.
6. **Delete.** `BeforeDelete` deletes the [PROVIDER_LOCATION](../database-tables.md#provider_location) row by `ProviderLocationSeqNum` ([SQL-098](../sql/SQL-098-delete-provider-location.md)); the delete statement does not include an `UPDATE_NUM` concurrency check.
   - On a rows-affected value other than `1`: add the DB contention error.
   - On success: call `clsContactApi.RemoveContact(...)` (route `provider/{providerlocationSeqNumber}/remove`); API failures are logged and surfaced as `ApiDeleteError`.
   - The delete path does **not** run any of the payment/audit/note usage checks that exist elsewhere in the codebase — a commented-out `IsBeingUsed` guard suggests protective checks against `HasPayment` (`CL_FINANCIAL_TRANS`), `HasAudit` (`CBP_AUDITED_SERVICE`), and `HasNote` (`CBP_NOTE`) were planned but are not active.*

## Process flow

```mermaid
flowchart TD
	Start([Caller creates or edits location]) --> Rules{Validation rules pass?}
	Rules -->|No| Fail([Broken rules returned])
	Rules -->|Yes| Which{Save or Delete?}
	Which -->|Save/Insert| Insert[Insert PROVIDER_LOCATION]
	Which -->|Save/Update| Update[Update PROVIDER_LOCATION]
	Which -->|Delete| Delete[Delete PROVIDER_LOCATION,<br>no in-use check]
	Insert --> RowsIns{RowsAffected = 1?}
	Update --> RowsUpd{RowsAffected = 1?}
	Delete --> RowsDel{RowsAffected = 1?}
	RowsIns -->|No| InsErr([Insert Failed])
	RowsIns -->|Yes| ApiAdd[Call clsContactApi.AddContact]
	RowsUpd -->|No| Contention([DB contention error])
	RowsUpd -->|Yes| ApiUpd[Call clsContactApi.UpdateContact]
	RowsDel -->|No| Contention
	RowsDel -->|Yes| ApiDel[Call clsContactApi.RemoveContact]
	ApiAdd --> Done([Contact Manager synced;<br>API failures logged, not blocking])
	ApiUpd --> Done
	ApiDel --> Done
```

## Failure and edge-case behavior

- Deletion has no in-use protection against existing payments, audits, or notes, unlike the analogous relationship-in-use check on `clsProviderRelationship`.*
- API synchronization failures (insert/update/delete) are logged and surfaced as error messages, but the underlying database change is never rolled back — the location can end up out of sync with Contact Manager.*
- A rows-affected value other than `1` is treated uniformly as either an insert failure or DB contention error across insert, update, and delete.

## 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\clsProviderLocation.vb`
- `Business\SAIF.Business.Provider\Api\clsContactApi.vb`
- `Business\SAIF.Business.Provider\clsProviderUtility.vb`
