---
title: Audit and audited services
summary: Persists the audit header for a bill together with its audited-service lines, service-level adjustments, and audit messages.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.Bill
reference_type: package
operation_id: OP-CB-02
trigger: clsBill.GenerateAudit() creates the audit; callers also load and edit the audit directly through clsAudit.GetAudit/NewAudit
---

# Audit and audited services

An audit is the aggregate record of how a bill's services were reviewed: the audit header carries bill-level totals and flags, each service on the bill gets its own audited-service line, each audited-service line can carry one or more payment adjustments, and the audit as a whole can accumulate error, warning, and informational messages. All four record types share the same bill-scoped lifecycle and the same rows-affected concurrency contract, and they are saved together whenever the audit is saved — either directly by a caller or as a cascade from [Bill CRUD](bill-crud.md).

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CB-02 |
| **Trigger** | `clsBill.GenerateAudit()` creates a new audit and its audited-service lines as part of a bill save; callers can also call `clsAudit.NewAudit(...)`/`GetAudit(...)` directly and edit the audited-service, adjustment, and message collections before calling `Save()` |
| **Inputs** | `BillID`; audit header fields (`ChargeableCostFlag`, `PrintEobFlag`, `AuditReceiptDate`, `SpecialHandlingCode`, total amounts); per-service audited fields (service/provider/date/amount/diagnosis/prescriber); adjustment code and amount per audited service; message category and text |
| **Outputs** | Populated `clsAudit` with its child collections on retrieve; persisted `CBP_AUDIT`, `CBP_AUDITED_SERVICE`, `CBP_AUDITED_SERVICE_ADJUSTMENT`, and `CBP_AUDIT_MESSAGE` rows on save; broken rules and DB-contention errors on failure |
| **Primary components** | `clsAudit.NewAudit`, `clsAudit.GetAudit`, `clsAudit.OnSave`, `clsAuditedServiceCollection.NewAuditedService`, `clsAuditedServiceAdjustmentCollection.NewServiceAdjustment`, `clsAuditMessageCollection.NewMessage`, `clsAuditMessage.NewErrorMessage`/`NewWarningMessage`/`NewInformationMessage` |
| **Entry point** | `Business\SAIF.Business.Claim.Bill\Core\clsAudit.vb` |

## Steps

1. **Create a new audit.** `clsAudit.Create()` seeds `BillID` from the criteria and creates empty child collections: `clsAuditMessageCollection.NewMessageCollection`, `clsAuditedServiceAdjustmentCollection.NewAuditedServiceAdjustmentCollection`, and `clsAuditedServiceCollection.NewAuditedServiceCollection`.
2. **Retrieve an existing audit.** `GetAudit(...)` builds a `Criteria(BillID)` and retrieves through `MyCriteria.RetrieveSQL`; `AfterRetrieve` loads the first row into the object, then `MarkOld()`.
3. **Insert the audit header.** `BeforeInsert`/`AfterInsert` write a single row into `CBP_AUDIT` with the bill ID, flags, receipt date, special-handling code, and total amounts ([SQL-003](../sql/SQL-003-saif-business-claim-bill.md), [CBP_AUDIT](../database-tables.md#cbp_audit)).
   - On a rows-affected value other than `1`: add `"Insert Failed"` ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)).
4. **Update the audit header.** `BeforeUpdate`/`AfterUpdate` update `CBP_AUDIT` by `BILL_ID` with optimistic concurrency on `UPDATE_NUM` ([SQL-032](../sql/SQL-032-saif-business-claim-bill.md), [CBP_AUDIT](../database-tables.md#cbp_audit)).
   - On a rows-affected value other than `1`: add a DB-contention error ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)).
5. **Delete the audit header.** `BeforeDelete`/`AfterDelete` delete the `CBP_AUDIT` row for the bill ([SQL-042](../sql/SQL-042-saif-business-claim-bill.md), [CBP_AUDIT](../database-tables.md#cbp_audit)); child collections are cleared rather than saved when the parent is deleted.
   - On a rows-affected value other than `1`: add a DB-contention error ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)).
6. **Stamp update fields (`OnSave`).** `UpdateDate = Now` and `UpdateUser` from the data-portal user are stamped on the header before every insert/update/delete.
7. **Add a new audited-service line.** `clsAuditedServiceCollection.NewAuditedService()` auto-assigns the next `ServiceID` as `Max(existing) + 1` (or `1`). `NewAuditedService(inServiceID)` instead requires an explicit service ID and first checks the collection for a duplicate.
   - If a line with that `ServiceID` already exists: throw `"Attempt to generate new Audit Service Line for service #<id> failed. - Service already exists."` ([BR-005](../business-rules.md#br-005-duplicate-audited-service-line-for-the-same-service-id-is-rejected)).
8. **Persist an audited-service line.** `clsAuditedService.Create()` seeds DPI, `BillID`, `ServiceID`, and update user.
   - **Insert:** writes the full audited-service row — service, provider, date, amount, diagnosis, and prescriber fields — into `CBP_AUDITED_SERVICE` ([SQL-004](../sql/SQL-004-saif-business-claim-bill.md), [CBP_AUDITED_SERVICE](../database-tables.md#cbp_audited_service)).
   - **Update:** updates nearly all service columns keyed by `BILL_ID + AUDITEDSERVICE_ID + UPDATE_NUM` ([SQL-033](../sql/SQL-033-saif-business-claim-bill.md), [CBP_AUDITED_SERVICE](../database-tables.md#cbp_audited_service)).
   - **Delete:** deletes by `BillID` and `AuditedserviceID` ([SQL-043](../sql/SQL-043-saif-business-claim-bill.md), [CBP_AUDITED_SERVICE](../database-tables.md#cbp_audited_service)).
   - Each path enforces the shared concurrency rule ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)).
   - Reference fields (claim bill type, original-bill code, bill status, diagnosis type) resolve through `mdlCBPSupportingData` code maps, and service descriptions resolve through the linked service's description list.

   > **Origin**: The reference-code resolution (`GetClaimBillTypeCodeMap`, `GetOriginalBillCodeMap`, `GetBillStatusCodeMap`, `GetDiagnosisTypeCodeMap`) is implemented by `mdlCBPSupportingData` in this same library; the service-description lookup is implemented by [Service reference lookups](service-reference-lookups.md) (`clsServiceInfo.ServiceDescriptions`).
9. **Validate and track dirtiness across the aggregate.** `clsAuditedService.IsValid()` validates its own state together with its nested adjustment collection and returns `ErrorCount = 0`; `IsDirty()` folds in the adjustment collection's dirty state, so a dirty adjustment makes its parent audited-service line dirty too.
10. **Add a new service-level adjustment.** `clsAuditedServiceAdjustmentCollection.NewServiceAdjustment(...)` creates the child via `clsAuditedServiceAdjustment.NewAuditedServiceAdjustment(...)`, sets the adjustment code and amount, and adds it to the list. `clsAuditedServiceAdjustment.Create()` seeds `BillID` and `AuditServiceID`.
    - **Insert:** writes the adjustment row into `CBP_AUDITED_SERVICE_ADJUSTMENT` ([SQL-005](../sql/SQL-005-saif-business-claim-bill.md), [CBP_AUDITED_SERVICE_ADJUSTMENT](../database-tables.md#cbp_audited_service_adjustment)).
    - **Update:** updates code/amount keyed by `BILL_ID + AUDITSERVICE_ID + CODE + UPDATE_NUM` ([SQL-034](../sql/SQL-034-saif-business-claim-bill.md), [CBP_AUDITED_SERVICE_ADJUSTMENT](../database-tables.md#cbp_audited_service_adjustment)).
    - **Delete:** by `BillID`, `AuditServiceID`, and adjustment code ([SQL-044](../sql/SQL-044-saif-business-claim-bill.md), [CBP_AUDITED_SERVICE_ADJUSTMENT](../database-tables.md#cbp_audited_service_adjustment)).
    - Each path enforces the shared concurrency rule ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)).
    - `SettlementGroup`, `AdjustmentType`, and `AuditedAdjustmentCategory` all resolve through `mdlCBPSupportingData` reference maps ([SQL-011](../sql/SQL-011-saif-business-claim-bill.md), [SQL-013](../sql/SQL-013-saif-business-claim-bill.md)).
11. **Add a new audit message.** `clsAuditMessageCollection.NewMessage(...)` optionally creates a temporary read-only in-memory message (assigning the next local `MessageID`) before always replacing it with a persisted-style object created via `NewWarningMessage`, `NewInformationMessage`, or `NewErrorMessage`, which is then added to the collection.
    - `Add(Item)` on the collection is blocked directly and throws `"Status can only be added via a Bill"`* — the same guard text used by `clsBillStatusCollection`, so new messages can only be introduced through the factory methods.
12. **Persist an audit message.** `clsAuditMessage.Create()` assigns `BillID`, allocates `MessageID` through `GetLegacyID(...)` (legacy CBP sequence, `CBPIDType.AuditError`), sets the category, and sets the message text.
    - **Insert:** writes the message row into `CBP_AUDIT_MESSAGE` ([SQL-006](../sql/SQL-006-saif-business-claim-bill.md), [CBP_AUDIT_MESSAGE](../database-tables.md#cbp_audit_message)).
    - **Update:** updates text/category keyed by `MESSAGE_ID + BILL_ID + UPDATE_NUM` ([SQL-035](../sql/SQL-035-saif-business-claim-bill.md), [CBP_AUDIT_MESSAGE](../database-tables.md#cbp_audit_message)).
    - **Delete:** by `MessageID` and `BillID` ([SQL-045](../sql/SQL-045-saif-business-claim-bill.md), [CBP_AUDIT_MESSAGE](../database-tables.md#cbp_audit_message)).
    - Each path enforces the shared concurrency rule ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)).

    > **Origin**: `MessageID` allocation reuses the same `GetLegacyID` / `GetNextCBPSeqNum` sequence path as [Bill CRUD](bill-crud.md) step 1, calling the Oracle procedure `sp82385_get_next_bill_seqnum` with `CBP_AUDIT_ERROR`.
13. **Complete the save.** `OnSaveComplete()` on the audit header saves the audited-service, adjustment, and message collections after the header itself is inserted, updated, or deleted; if the audit was deleted, the child collections are cleared instead of saved.

## Process flow

The following diagram visualizes how an audit and its three child record types are validated and persisted together.

```mermaid
flowchart TD
	Start([Bill save triggers GenerateAudit,<br>or caller edits audit directly]) --> Header[Create/retrieve<br>Audit header]
	Header --> HeaderSave[Insert/Update/Delete CBP_AUDIT]
	HeaderSave --> Rows{RowsAffected = 1?}
	Rows -->|No| Contention([Contention error])
	Rows -->|Yes| Services[For each bill service:<br>NewAuditedService]
	Services --> Dup{ServiceID already<br>on audit?}
	Dup -->|Yes| DupErr([Duplicate-service exception])
	Dup -->|No| SaveService[Insert/Update/Delete<br>CBP_AUDITED_SERVICE]
	SaveService --> Adjust[Optional: NewServiceAdjustment<br>per audited service]
	Adjust --> SaveAdjust[Insert/Update/Delete<br>CBP_AUDITED_SERVICE_ADJUSTMENT]
	SaveService --> Messages[Optional: NewMessage<br>error / warning / information]
	Messages --> SaveMsg[Insert/Update/Delete<br>CBP_AUDIT_MESSAGE]
	SaveAdjust --> Done([Save returns success])
	SaveMsg --> Done
```

## Failure and edge-case behavior

- All four record types (audit header, audited service, adjustment, message) share the identical rows-affected concurrency contract; the caller cannot distinguish which table failed from the error text alone ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)).
- Adding a duplicate `ServiceID` to an audit throws immediately rather than returning a broken-rule collection entry, so callers must catch the exception or pre-check for an existing line ([BR-005](../business-rules.md#br-005-duplicate-audited-service-line-for-the-same-service-id-is-rejected)).
- Deleting the audit header clears rather than saves its child collections, so a deleted audit never leaves orphaned or stale audited-service, adjustment, or message rows from the same save cycle.
- `clsAuditMessageCollection.Add()` reuses the exact guard message text from `clsBillStatusCollection` (`"Status can only be added via a Bill"`), which does not describe audit messages — this appears to be copied guard text rather than a message-specific string.*
- `clsAuditMessage` field `UpdateNumer` (not `UpdateNumber`) is used in several places in source; this spelling is preserved here only to describe the code accurately, not as a documentation error.*

## 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.Claim.Bill\Core\clsAudit.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsAuditCollection.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsAuditedService.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsAuditedServiceCollection.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsAuditedServiceAdjustment.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsAuditedServiceAdjustmentCollection.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsAuditMessage.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsAuditMessageCollection.vb`
- `Business\SAIF.Business.Claim.Bill\AuditSupport\BillStatus.vb`
- `Business\SAIF.Business.Claim.Bill\AuditSupport\BillType.vb`
- `Business\SAIF.Business.Claim.Bill\AuditSupport\OriginalBills.vb`
- `Business\SAIF.Business.Claim.Bill\AuditSupport\mdlCBPSupportingData.vb`
