Customer CRUD with child persistence¶
clsCustomer owns the root COMPANY row and lazily exposes its address, email, phone, customer-contact relationship, and long-note children as properties. A single Save() call persists the root row first, then cascades to every child collection in a fixed order so link rows always reference an existing parent.
| Field | Value |
|---|---|
| Operation ID | OP-CC-02 |
| Trigger | clsCustomer.NewCustomer(dpi) or clsCustomer.GetCustomer(dpi, customerID), followed by property/child edits and Save() |
| Inputs | Root customer fields (name, type code, tax ID, active flag, internal reference ID), LongNoteText, and any of the Addresses, Emails, Phones, Contacts child collections |
| Outputs | Persisted COMPANY row; persisted child address/email/phone/contact-relationship/note rows; InsertError/UpdateError/DeleteError broken rules on contention |
| Primary components | clsCustomer.Create, clsCustomer.GetCustomer, clsCustomer.OnSave, clsCustomer.OnSaveComplete, clsCustomer.BeforeInsert, clsCustomer.BeforeUpdate, clsCustomer.BeforeDelete |
| Entry point | Business\SAIF.Business.CustomerContact\Customer\clsCustomer.vb |
Steps¶
- Create a new customer.
Create()assigns a newCustomerIDfromGetNextSeqNum("COMPANY_SEQ")on the data-portal info. - Retrieve an existing customer.
GetCustomer(dpi, customerID)runs the BOF retrieve pipeline (BeforeRetrieve/AfterRetrieve) againstCOMPANY(SQL-035, COMPANY); theAddresses,Emails,Phones,Contacts, andCustomerNotechild properties are lazy-loaded the first time they are accessed rather than during retrieve. - Save pre-processing (
OnSave). - If the customer
IsDeleted: clearEmails,Phones, andAddressesin memory, callCustomerNote.Delete(), and immediately save those four child objects before the root delete proceeds. - Otherwise: set
_UpdateDate = Nowand_UpdateUserfrom the data-portal info user ID. - If the existing
CustomerNoteis not new and itsNoteTextis blank, callCustomerNote.Delete()so the note row is removed rather than stored empty (BR-009). - Persist the root row.
- Insert:
BeforeInsertbinds all rootCOMPANYfields (SQL-008, COMPANY).- On failure (rows affected ≠ 1): add the
InsertErrorbroken rule.
- On failure (rows affected ≠ 1): add the
- Update:
BeforeUpdatere-binds the same field set (SQL-068, COMPANY).- On failure (rows affected ≠ 1): add the
UpdateErrorbroken rule with the DB-contention message.
- On failure (rows affected ≠ 1): add the
- Delete:
BeforeDeleteissuesDelete COMPANY Where COMPANY_SEQ_NUM = :CustomerID(SQL-082, COMPANY).- On failure (rows affected ≠ 1): add the
DeleteErrorbroken rule.
- On failure (rows affected ≠ 1): add the
- Save children after the root row (
OnSaveComplete). In this fixed order:Addresses,Emails,Phones,Contacts, thenCustomerNote. Each child collection or object persists through its own criteria and SQL (see Address info CRUD and linking, Email info CRUD and linking, Phone info CRUD and linking, Customer-contact relationship CRUD, Long-note persistence). - Resolve the customer-type description.
CustomerTypeDescriptioncallsGetCustomerCode(dpi)(CustomerTypeCode).Descriptionfrom the sharedSAIF.Businesscode-map module to translate the stored type code into a display description; this is a lookup only and performs no persistence.
Process flow¶
The following diagram visualizes the customer save cascade.
flowchart TD
Start([Caller calls Save]) --> Deleted{IsDeleted?}
Deleted -->|Yes| ClearChildren[Clear Emails, Phones, Addresses;<br>delete CustomerNote;<br>save those four now]
ClearChildren --> DeleteRoot[Delete COMPANY row]
Deleted -->|No| SetAudit[Set UpdateDate, UpdateUser]
SetAudit --> NoteCheck{CustomerNote not new<br>and NoteText blank?}
NoteCheck -->|Yes| DeleteNote[Delete CustomerNote row]
NoteCheck -->|No| RootSave
DeleteNote --> RootSave[Insert or update COMPANY row]
RootSave --> RowCheck{RowsAffected = 1?}
RowCheck -->|No| Error([InsertError / UpdateError added])
RowCheck -->|Yes| Cascade[OnSaveComplete:<br>save Addresses, Emails,<br>Phones, Contacts, CustomerNote]
DeleteRoot --> RowCheck2{RowsAffected = 1?}
RowCheck2 -->|No| DeleteError([DeleteError added])
RowCheck2 -->|Yes| Done2([Customer removed])
Cascade --> Done([Save complete])
Failure and edge-case behavior¶
- A contention failure on the root insert/update/delete adds the corresponding broken rule, but the child-save cascade in
OnSaveCompletestill runs unconditionally afterward; there is no explicit short-circuit tied to the root row's row-count check.* - On delete,
Contacts(the customer-contact relationship collection) is not cleared or explicitly cleaned up inOnSave, unlikeEmails,Phones, andAddresses; anyCOMPANY_PERSONcleanup for a deleted customer is not evident in this class.* - Removing an item from
Addresses,Emails, orPhonesdeletes only the link row (COMPANY_ADDRESS,COMPANY_EMAIL,COMPANY_PHONE); the standaloneADDRESS/EMAIL_ADDRESS/PHONErow is not deleted as part of this cascade. - A blank long note is deleted rather than saved as an empty string, so retrieving a customer with no note always yields a fresh, clean note object rather than a stored blank row (BR-009).
Note: Statements marked with
*were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.
Related documentation¶
Sources¶
Business\SAIF.Business.CustomerContact\Customer\clsCustomer.vb