---
title: Bill CRUD
summary: Creates, retrieves, updates, and deletes a medical bill header and cascades the save into its dependent diagnosis, procedure, service, and status collections.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.Bill
reference_type: package
operation_id: OP-CB-01
trigger: Caller invokes clsBill.NewBill / clsBill.GetBill and later Save()
---

# Bill CRUD

Bill CRUD is the central maintenance operation for a medical bill. It allocates a bill identifier from the legacy CBP sequence generator, loads or persists the `CBP_BILL` header row, generates an audit whenever a bill is saved, and cascades the save into four dependent child collections — diagnosis, procedure, service, and status — each of which owns its own table and its own optimistic-concurrency check. The header itself carries only one explicit business rule (a non-zero bill ID); the remaining validation surface is the shared concurrency contract enforced identically across the header and every child collection.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CB-01 |
| **Trigger** | Caller calls `clsBill.NewBill(inDataportal)` to create a bill or `clsBill.GetBill(inDataportal, inBillID)` to load one, edits properties and child collections, and calls `Save()` |
| **Inputs** | `BillID` (retrieve/create key); mutable header fields (bill type, entry source, status, dates, amounts); child collection edits (`Diagnosis`, `Procedures`, `Services`, `StatusHistory`); `IDataPortalInfo` |
| **Outputs** | Populated `clsBill` instance on retrieve; persisted header row plus cascaded child-collection rows and a generated `clsAudit` on save; broken rules and DB-contention errors on failure |
| **Primary components** | `clsBill.NewBill`, `clsBill.GetBill`, `clsBill.Create`, `clsBill.BeforeInsert`/`AfterInsert`, `clsBill.BeforeUpdate`/`AfterUpdate`, `clsBill.BeforeDelete`/`AfterDelete`, `clsBill.OnSave`, `clsBill.OnSaveComplete`, `clsBill.GenerateAudit`, `clsBill.AddBusinessRules` |
| **Entry point** | `Business\SAIF.Business.Claim.Bill\Core\clsBill.vb` |

## Steps

1. **Create a new bill.** `NewBill(inDataportal)` calls `DataAccessLayer.CreateObject(New BillCriteria(inDataportal, 0))`, which dispatches to `Create()`. `Create()` allocates a new `BillID` through `GetLegacyID(MyCriteria.DataPortalInfo)`, which resolves the legacy CBP sequence.
   - Sub-step: `GetLegacyID` (`clsLegacyIDBase`) calls `GetNextCBPSeqNum(inDPI, LegacyIDType)` for `CBPIDType.Bill`, which invokes the Oracle stored procedure and returns the next sequence value for `CBP_BILL`, or throws `"Failed to retrieve new ID from Legacy System"` if no value comes back ([SQL-012](../sql/SQL-012-saif-business-claim-bill.md)).

   > **Origin**: The sequence allocation is implemented by `mdlCBPSupportingData.GetNextCBPSeqNum`, which calls the Oracle procedure `sp82385_get_next_bill_seqnum`. See [Stored procedure operations](../operations-processes.md) and [PR-001](../business-rules.md#pr-001-map-intable-values-to-the-correct-cbp-sequence).
2. **Retrieve an existing bill.** `GetBill(inDataportal, inBillID)` calls `DataAccessLayer.RetrieveObject(New BillCriteria(...))`. `BeforeRetrieve` assigns the bill-select SQL against `CBP_BILL` outer-joined to `CBP_AUDIT` ([SQL-001](../sql/SQL-001-saif-business-claim-bill.md) family; retrieve path). `AfterRetrieve` fills the object from the first row if present, then calls `MarkOld()`.
3. **Enforce the header validation rule.** `AddBusinessRules()` registers `CommonBusinessRules.NonZeroDecimalRequired` against `BillID`, producing `"Invalid Bill ID"` when the bill ID is zero or unset ([BR-002](../business-rules.md#br-002-bill-id-must-be-non-zero)).
4. **Caller edits header and child collections.** Property setters on `clsBill` mark the header dirty; `Diagnosis`, `Procedures`, `Services`, and `StatusHistory` are independently editable collections that track their own dirty state.
5. **Insert a new header.** `BeforeInsert`/`AfterInsert` build a non-query insert into `CBP_BILL` with all header columns ([SQL-001](../sql/SQL-001-saif-business-claim-bill.md), [CBP_BILL](../database-tables.md#cbp_bill)).
   - 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)).
   - On success: `MarkOld()`.
6. **Update the header.** `BeforeUpdate` first checks `MyBase.IsDirty`; if the header has no changes, the update is skipped entirely and only dirty child collections are saved. When dirty, it issues a non-query update against `CBP_BILL` keyed by `BILL_ID` and `UPDATE_NUM` ([SQL-030](../sql/SQL-030-saif-business-claim-bill.md), [CBP_BILL](../database-tables.md#cbp_bill)) ([BR-002](../business-rules.md#br-002-bill-id-must-be-non-zero)).
   - 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)).
7. **Delete the header.** `BeforeDelete`/`AfterDelete` delete the `CBP_BILL` row by `BILL_ID` ([SQL-040](../sql/SQL-040-saif-business-claim-bill.md), [CBP_BILL](../database-tables.md#cbp_bill)).
   - 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)).
8. **Stamp audit fields (`OnSave`).** Before each insert/update/delete, `OnSave()` sets `UpdateDate = Now` and `UpdateUser` from the data-portal user.
   - If the bill is marked deleted: the audit for the bill is deleted and the child collections are cleared instead of saved.
9. **Persist the diagnosis collection.** When `Diagnosis` is dirty, `clsBillDiagnosisCollection.Save()` calls `ApplyEdit()` then `MyBase.Save()`, cascading to each `clsBillDiagnosis` item.
   - **Create:** `NewDiagnosis(inDataportal, inBillID)` sets `BillID` from criteria; insert writes `BillID`, `DiagnosisCode`, `DiagnosisTypeCode`, `DiagnosisPointerCode`, `UpdateUser`, `UpdateDate` into `CBP_BILL_DIAGNOSIS` ([SQL-007](../sql/SQL-007-saif-business-claim-bill.md), [CBP_BILL_DIAGNOSIS](../database-tables.md#cbp_bill_diagnosis)).
   - **Update:** keyed by `BILL_ID + DIAGNOSIS_CODE + UPDATE_NUM`, incrementing `UPDATE_NUM` ([SQL-036](../sql/SQL-036-saif-business-claim-bill.md), [CBP_BILL_DIAGNOSIS](../database-tables.md#cbp_bill_diagnosis)).
   - **Delete:** keyed by `BILL_ID + DIAGNOSIS_CODE` ([SQL-046](../sql/SQL-046-saif-business-claim-bill.md), [CBP_BILL_DIAGNOSIS](../database-tables.md#cbp_bill_diagnosis)).
   - Every insert/update/delete enforces the shared concurrency rule ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)); no object-level business rules are registered on `clsBillDiagnosis`.
10. **Persist the procedure collection.** `clsBillProcedureCollection.NewProcedure(...)` computes the next `BillProcedureID` as `Max(existing) + 1` (or `1` if none exist), then delegates to `clsBillProcedure.NewProcedure(inDataportal, inBillID, inBillProcedureID)`.
    - **Create:** insert writes procedure fields into `CBP_BILL_PROCEDURE` ([SQL-008](../sql/SQL-008-saif-business-claim-bill.md), [CBP_BILL_PROCEDURE](../database-tables.md#cbp_bill_procedure)); `Service` and `Procedure` property lookups resolve reference data through the service manager and procedure-code list.
    - **Update:** keyed by `BILL_ID + BILL_PROCEDURE_ID + UPDATE_NUM` ([SQL-037](../sql/SQL-037-saif-business-claim-bill.md), [CBP_BILL_PROCEDURE](../database-tables.md#cbp_bill_procedure)).
    - **Delete:** by `BILL_ID + BILL_PROCEDURE_ID` ([SQL-047](../sql/SQL-047-saif-business-claim-bill.md), [CBP_BILL_PROCEDURE](../database-tables.md#cbp_bill_procedure)).
    - Concurrency failures follow [BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1); no object-level rules registered.

    > **Origin**: The `Service` lookup used during procedure edits is implemented by [Service reference lookups](service-reference-lookups.md) (`clsServiceManager.GetService`) in this same library.
11. **Persist the service collection.** `clsBillServiceCollection.NewService()` computes the next `ServiceID` as `Max(existing) + 1` (or `1`), then delegates to `clsBillService.NewService(...)`, which sets `BillID`, `ServiceID`, `UpdateUser`, and defaults `CompoundDrugFlag = "N"`.
    - **Create:** insert writes service fields into `CBP_BILL_SERVICE` ([SQL-009](../sql/SQL-009-saif-business-claim-bill.md), [CBP_BILL_SERVICE](../database-tables.md#cbp_bill_service)); `ServiceDescription`/`SecondaryServiceDescription` resolve through the linked service's description list at the service date.
    - **Update:** keyed by `BILL_ID + SERVICE_ID + UPDATE_NUM` ([SQL-038](../sql/SQL-038-saif-business-claim-bill.md), [CBP_BILL_SERVICE](../database-tables.md#cbp_bill_service)).
    - **Delete:** by `BILL_ID + SERVICE_ID` ([SQL-048](../sql/SQL-048-saif-business-claim-bill.md), [CBP_BILL_SERVICE](../database-tables.md#cbp_bill_service)).
    - Concurrency failures follow [BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1); no object-level rules registered.

    > **Origin**: The service-description lookup is implemented by [Service reference lookups](service-reference-lookups.md) (`clsServiceInfo.ServiceDescriptions`) in this same library.
12. **Persist the status-history collection.** `clsBillStatusCollection.NewStatus(...)` blocks direct `Add()` calls with `"Status can only be added via a Bill"`, so new status rows can only be created through the collection's own factory methods; each new item resolves its own identifier through `Criteria.IsLegacy` — either `GetLegacyID(...)` for legacy-style IDs or `GetNextSeqNum("BILL_STATUS_SEQ")` otherwise.
    - **Create:** insert writes status fields into `CBP_BILL_STATUS` ([SQL-010](../sql/SQL-010-saif-business-claim-bill.md), [CBP_BILL_STATUS](../database-tables.md#cbp_bill_status)); `StatusCode` resolves through `GetBillStatusCodeMap(...)`.
    - **Update:** keyed by `BILL_ID + STATUS ID + UPDATE_NUM` ([SQL-039](../sql/SQL-039-saif-business-claim-bill.md), [CBP_BILL_STATUS](../database-tables.md#cbp_bill_status)).
    - **Delete:** by bill and status identifier ([SQL-049](../sql/SQL-049-saif-business-claim-bill.md), [CBP_BILL_STATUS](../database-tables.md#cbp_bill_status)).
    - Concurrency failures follow [BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1); no object-level rules registered (`AddBusinessRules` is empty).
13. **Generate the audit on save (`GenerateAudit` / `OnGenerateAudit`).** After the header and child collections are saved, `GenerateAudit()` creates `clsAudit.NewAudit(...)` for the bill, sets `AuditReceiptDate` and `ChargeableCostFlag`, and then, for each `BillService` in the saved collection, calls `Audit.AuditedServices.NewAuditedService(BillService.ServiceID)` and copies service, provider, date, amount, diagnosis, and prescriber fields onto the new audited-service line, applying conditional null checks per field.
    - If a duplicate `ServiceID` already exists on the audit: the audited-service factory rejects it ([BR-005](../business-rules.md#br-005-duplicate-audited-service-line-for-the-same-service-id-is-rejected)).

    > **Origin**: The generated audit and its audited-service lines are persisted by [Audit and audited services](audit-and-audited-services.md) (`clsAudit`, `clsAuditedService`), which this step invokes to record the bill's audit trail.
14. **Complete the save (`OnSaveComplete`).** After the header insert/update/delete succeeds, `OnSaveComplete()` saves `Procedures`, `Diagnosis`, `Services`, and `StatusHistory` (each only if dirty) and persists the generated `Audit`. If the bill itself was deleted, the audit is deleted and the child collections are cleared rather than saved.

## Process flow

The following diagram visualizes the bill save cascade from header validation through child-collection persistence and audit generation.

```mermaid
flowchart TD
	Start([Caller edits Bill header<br>and child collections]) --> Rule{BillID non-zero?}
	Rule -->|No| Fail([Broken rule;<br>save refused])
	Rule -->|Yes| HeaderDirty{Header dirty?}
	HeaderDirty -->|Yes| HeaderSave[Insert/Update/Delete<br>CBP_BILL]
	HeaderDirty -->|No| SkipHeader[Skip header write]
	HeaderSave --> Rows{RowsAffected = 1?}
	Rows -->|No| Contention([Contention error])
	Rows -->|Yes| Children
	SkipHeader --> Children[Save dirty child collections:<br>Diagnosis, Procedure, Service, Status]
	Children --> Audit[GenerateAudit:<br>create Audit + Audited Service lines]
	Audit --> Dup{Duplicate ServiceID<br>on audit?}
	Dup -->|Yes| DupErr([Duplicate-service exception])
	Dup -->|No| Complete[OnSaveComplete:<br>persist Audit and children]
	Complete --> Done([Save returns success])
```

## Failure and edge-case behavior

- A header that is not dirty skips its own update entirely; only dirty child collections are saved, so a caller that only edits a child collection never triggers a header-row rewrite.
- Every insert/update/delete across the header and all four child collections shares the same rows-affected contract; a `0` or unexpected row count is reported as `"Insert Failed"` or a DB-contention error rather than a distinguishing message per table ([BR-004](../business-rules.md#br-004-optimistic-concurrency-updates-fail-if-rowsaffected-does-not-equal-1)).
- If the bill is deleted, its audit is deleted and its child collections are cleared instead of saved — a deleted bill never triggers a child-collection insert/update.
- `clsBillStatusCollection.Add()` is deliberately blocked outside the collection's own factory methods, so any caller attempting to add a status object directly receives `"Status can only be added via a Bill"` rather than a silently accepted item.*
- `GetLegacyID` throws immediately if the Oracle sequence procedure returns no value, so a new bill or new legacy-style status row cannot be created at all without a valid sequence result.

## 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\clsBill.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillCollection.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillDiagnosis.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillDiagnosisCollection.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillProcedure.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillProcedureCollection.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillService.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillServiceCollection.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillStatus.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsBillStatusCollection.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsLegacyIDBase.vb`
- `Business\SAIF.Business.Claim.Bill\Core\clsServiceBase.vb`
- `Business\SAIF.Business.Claim.Bill\AuditSupport\mdlCBPSupportingData.vb`
