---
title: Maintain provider service and service contract
summary: Persists the services a provider location offers, the service-type reference link, and the pricing/contract terms tied to each service.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.Provider
reference_type: package
operation_id: OP-PRV-10
trigger: Caller creates or retrieves clsProviderService, clsProviderServiceContract, or clsProviderServiceType, edits properties, and calls Save() or Delete()
---

# Maintain provider service and service contract

Three closely related classes are documented together because they govern the same provider-location service catalog: `clsProviderService` records which services a location offers and which one is primary, `clsProviderServiceContract` records the pricing/contract terms for a service, and `clsProviderServiceType` records the service-type-to-service-code reference link. All three follow the standard BOF insert/update/delete pipeline with `UPDATE_NUM` concurrency on update.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-PRV-10 |
| **Trigger** | Caller calls `NewProviderService`/`NewProviderServiceContract`/`NewProviderServiceType` or the matching `Get*` factory, edits properties, and calls `Save()` or `Delete()` |
| **Inputs** | `ProviderLocationSeqNum`, `ProviderServiceCode`, `PrimaryServiceFlag`; contract fields `EffDate`/`ExpDate`, `PurchaseCode`, `DiscountRate`, `AdditionalFeeDiscountRate`, `MaxChargeAmt`, `CBPServiceCode`, `ServiceModifierCode`, `ServiceCode`, `ServiceTypeCode`, `MCOPlanID`, `ContactName`; `ProviderTypeCode` for the service-type link |
| **Outputs** | Persisted [PROVIDER_SERVICE](../database-tables.md#provider_service), [PROVIDER_SERVICE_CONTRACT](../database-tables.md#provider_service_contract), and [PROVIDER_SERVICE_TYPE](../database-tables.md#provider_service_type) rows |
| **Primary components** | `clsProviderService.AddBusinessRules`, `clsProviderServiceContract.AddBusinessRules`, `clsProviderServiceType` |
| **Entry point** | `Business\SAIF.Business.Provider\clsProviderService.vb` |

## Steps

1. **Create.** `clsProviderService.Create()` copies `ProviderLocationSeqNum` and stores the original service code for update targeting. `clsProviderServiceContract.Create()` copies `ProviderLocationSeqNum` and assigns a new `PROV_SERVICE_CONTRACT_SEQ` via `GetNextSeqNum(...)`. `clsProviderServiceType.Create()` performs no setup.
2. **Validate provider service before save.**
   - `CommonBusinessRules.TextRequired` on `ProviderServiceCode`: hard error requiring a service type.
   - [BR-019](../business-rules.md#br-019-exactly-one-primary-provider-service-is-allowed-per-location-without-duplicate-service-codes): `HasPrimaryService` requires exactly one row in the sibling `clsProviderServiceCollection` (`RelatedServices`) flagged primary; `HasDuplicatePrimaryService` blocks more than one; `HasDuplicateService` blocks the same `ProviderServiceCode` appearing twice.
3. **Validate service contract before save.**
   - `EffectiveMCOPlanID`: hard error — if dirty with `MCOPlanID` and a valid `EffDate` — running `IsMCOPlanEffective`, a count query against `MCO_PROVIDER` for the location/plan/effective-date combination.
   - `ValidCBPServiceCode`: hard error — if dirty with `CBPServiceCode` and `EffDate` — calling `clsCBPService.GetCBPService(...)` and requiring a nonblank `ServiceDescrText`.
   - `ValidServiceModifier`: hard error requiring `ServiceModifierCode` to be blank or `"SG"`.
   - [BR-020](../business-rules.md#br-020-provider-service-contract-pricing-fields-are-mutually-exclusive): `AmtOrRateRequired` (the three pricing fields must total more than zero) and `AmtAndRateNotZero` (no more than one of the three may be nonzero).
   - `ValidDiscountRate`: hard error — when present/nonzero — requiring a value `>= 0.45` and `< 1`. `ValidAdditionalFeeDiscountRate`: hard error rejecting values `>= 0.5` (the associated message describes a "greater than zero and less than .5" range, but the code only enforces the upper bound).*
   - `ValidEffDate`, `ValidEffExpDate`, `UnreasonableEffDate`: hard errors requiring a real effective date, no later than the expiration date, and no earlier than 01/01/1850.
   - [BR-021](../business-rules.md#br-021-package-priced-and-equipment-service-contract-rows-carry-additional-cross-field-requirements): `MCOPlanRequired` (package-price rows, `ServiceCode = Z`, require an MCO plan), `ServiceTypeCodeAndServiceCode` (mutually exclusive), `ServiceTypeCodeOrServiceCodeRequired` (one required), `PurchaseCodeRequired`/`PurchaseCodeInvalid` (equipment rows, `ServiceCode`/`ServiceTypeCode = E`, require a purchase code; other rows must leave it blank), `DiscountRateNotAllowed`/`AdditionalFeeDiscountRateNotAllowed` (package-price rows must not populate discount fields).
   - Warning-only: `MaxChargeAmtTooMuch` (amount over `1000`), `EarlyEffDate` (effective date before `01/01/2002` but not before 1850).
4. **Service type has no business rules.** `clsProviderServiceType.AddBusinessRules()` is empty; only `ProviderTypeCode` and `ProviderServiceCode` are persisted.
5. **Insert provider service.** Writes `PROVIDER_LOCATION_SEQ_NUM`, `PROVIDER_SERVICE_CODE`, `PRIMARY_SERVICE_FLAG`, and audit fields to [PROVIDER_SERVICE](../database-tables.md#provider_service) ([SQL-032](../sql/SQL-032-insert-provider-service.md)).
6. **Insert service contract.** Writes all contract fields — including `CONTACT_NAME` and `SERVICE_MODIFIER_CODE` — to [PROVIDER_SERVICE_CONTRACT](../database-tables.md#provider_service_contract) ([SQL-034](../sql/SQL-034-insert-provider-service-contract.md)).
7. **Insert service type.** Writes `PROVIDER_TYPE_CODE`, `PROVIDER_SERVICE_CODE`, and audit fields to [PROVIDER_SERVICE_TYPE](../database-tables.md#provider_service_type) ([SQL-035](../sql/SQL-035-insert-provider-service-type.md)); this table's implemented retrieve/update/delete key is **`PROVIDER_TYPE_CODE` only**.*
8. **Update.**
   - Provider service updates `WHERE PROVIDER_LOCATION_SEQ_NUM = :ProviderLocationSeqNum AND PROVIDER_SERVICE_CODE = :OriginalProviderServiceCode AND UPDATE_NUM = :UpdateNum` ([SQL-080](../sql/SQL-080-update-provider-service.md)); `AfterUpdate()` resets the tracked original service code.
   - Service contract updates `WHERE PROV_SERVICE_CONTRACT_SEQ_NUM = :ProvServiceContractSeqNum AND UPDATE_NUM = :UpdateNum` ([SQL-082](../sql/SQL-082-update-provider-service-contract.md)); `AfterUpdate()` unusually increments `m_ProvServiceContractSeqNum` locally in addition to the standard success handling.*
9. **Error handling.** All three classes follow the standard pattern: insert failure adds `"Insert Failed"`; update/delete failure adds the DB contention error.
10. **Retrieval gaps.** `clsProviderService.GetProviderService()` and `clsProviderServiceType.GetProviderServiceType()` retrieve **by their respective code column only** (not by location), so retrieval is effectively a code lookup rather than a location-scoped fetch.* `clsProviderServiceContract.GetProviderServiceContract()` exists as a factory but this class does **not** implement a working retrieve path — `Criteria.RetrieveSQL` throws `InvalidSQLProperty` — so contract rows must be accessed through their parent collection rather than retrieved individually.*

## Process flow

```mermaid
flowchart TD
	Start([Caller edits service or contract]) --> Which{Editing service, contract, or type?}
	Which -->|Service| PrimaryCheck{Exactly one primary,<br>no duplicate codes?}
	PrimaryCheck -->|No| Fail([Multiple primaries or<br>duplicate codes block save])
	PrimaryCheck -->|Yes| SaveService[Insert/update PROVIDER_SERVICE]
	Which -->|Contract| PricingCheck{Pricing fields mutually<br>exclusive and populated?}
	PricingCheck -->|No| Fail2([Conflicting pricing<br>fields block save])
	PricingCheck -->|Yes| CrossField{Package/equipment<br>cross-field rules pass?}
	CrossField -->|No| Fail3([Missing cross-field<br>data blocks save])
	CrossField -->|Yes| SaveContract[Insert/update PROVIDER_SERVICE_CONTRACT]
	Which -->|Type| SaveType[Insert/update PROVIDER_SERVICE_TYPE;<br>no business rules]
	SaveService --> Rows{RowsAffected = 1?}
	SaveContract --> Rows
	SaveType --> Rows
	Rows -->|No| Contention([Insert Failed / DB contention error])
	Rows -->|Yes| Done([Save complete])
```

## Failure and edge-case behavior

- `clsProviderServiceContract.GetProviderServiceContract()` is not functionally implemented — its `RetrieveSQL` throws `InvalidSQLProperty` — so individual contract rows can only be reached through the collection that loaded them.*
- `ValidAdditionalFeeDiscountRate`'s error message describes a different valid range than the code actually enforces (message implies "greater than zero and less than .5"; code only rejects `>= 0.5`).*
- `clsProviderServiceType`'s retrieve/update/delete key is `PROVIDER_TYPE_CODE` only, so a location can have at most one meaningfully addressable service-type row per provider type code regardless of `ProviderServiceCode`.*
- `clsProviderServiceContract.AfterUpdate()` increments the sequence number locally after a successful save, an unusual pattern not seen on the other classes in this operation.*

## 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\clsProviderService.vb`
- `Business\SAIF.Business.Provider\clsProviderServiceContract.vb`
- `Business\SAIF.Business.Provider\clsProviderServiceType.vb`
- `Business\SAIF.Business.Provider\clsCBPService.vb`
- `Business\SAIF.Business.Provider\clsMCOPlan.vb`
