Skip to content

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.
  2. 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).

Origin: The sequence allocation is implemented by mdlCBPSupportingData.GetNextCBPSeqNum, which calls the Oracle procedure sp82385_get_next_bill_seqnum. See Stored procedure operations and PR-001. 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 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). 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, CBP_BILL). - On a rows-affected value other than 1: add "Insert Failed" (BR-004). - 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, CBP_BILL) (BR-002). - On a rows-affected value other than 1: add a DB-contention error (BR-004). 7. Delete the header. BeforeDelete/AfterDelete delete the CBP_BILL row by BILL_ID (SQL-040, CBP_BILL). - On a rows-affected value other than 1: add a DB-contention error (BR-004). 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, CBP_BILL_DIAGNOSIS). - Update: keyed by BILL_ID + DIAGNOSIS_CODE + UPDATE_NUM, incrementing UPDATE_NUM (SQL-036, CBP_BILL_DIAGNOSIS). - Delete: keyed by BILL_ID + DIAGNOSIS_CODE (SQL-046, CBP_BILL_DIAGNOSIS). - Every insert/update/delete enforces the shared concurrency rule (BR-004); 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, 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, CBP_BILL_PROCEDURE). - Delete: by BILL_ID + BILL_PROCEDURE_ID (SQL-047, CBP_BILL_PROCEDURE). - Concurrency failures follow BR-004; 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.
  1. 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, 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, CBP_BILL_SERVICE).
    • Delete: by BILL_ID + SERVICE_ID (SQL-048, CBP_BILL_SERVICE).
    • Concurrency failures follow BR-004; no object-level rules registered.

    Origin: The service-description lookup is implemented by Service reference lookups (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, CBP_BILL_STATUS); StatusCode resolves through GetBillStatusCodeMap(...). - Update: keyed by BILL_ID + STATUS ID + UPDATE_NUM (SQL-039, CBP_BILL_STATUS). - Delete: by bill and status identifier (SQL-049, CBP_BILL_STATUS). - Concurrency failures follow BR-004; 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).

    Origin: The generated audit and its audited-service lines are persisted by Audit and audited services (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.

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).
  • 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.

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