Contact CRUD with child persistence¶
clsContact owns the root PERSON row and exposes address, email, phone, and long-note children the same way clsCustomer does. The key difference is a delete-time business rule: a contact cannot be deleted while it is still linked to any customer through COMPANY_PERSON.
| Field | Value |
|---|---|
| Operation ID | OP-CC-03 |
| Trigger | clsContact.NewContact(dpi) or clsContact.GetContact(dpi, contactID), followed by property/child edits and Save() |
| Inputs | Root contact fields (composite Name, split into FirstName/LastName/MiddleInitial/PreferredName/NameExtension), LongNoteText, and any of the Addresses, Emails, Phones child collections |
| Outputs | Persisted PERSON row; persisted child address/email/phone/note rows; delete blocked by a broken rule while relationships exist; InsertError/UpdateError/DeleteError broken rules on contention |
| Primary components | clsContact.Create, clsContact.GetContact, clsContact.OnSave, clsContact.OnSaveComplete, clsContact.ValidateNoRelationshipsOnDelete, clsContact.GetRelationships |
| Entry point | Business\SAIF.Business.CustomerContact\Contact\clsContact.vb |
Steps¶
- Create a new contact.
Create()assigns a newContactIDfromGetNextSeqNum("PERSON_SEQ"), and setsCreateDateandCreateUserfrom the data-portal info. - Retrieve an existing contact.
GetContact(dpi, contactID)runs the BOF retrieve pipeline againstPERSON(SQL-029, PERSON);Addresses,Emails,Phones, andContactNoteare lazy-loaded on first access. - Save pre-processing (
OnSave). - If the contact
IsDeleted: clearEmails,Phones, andAddresses, callContactNote.Delete(), save those four child objects, and return immediately — the parent delete path below still runs, but no further composite-name or note-blank processing happens on this branch. - Otherwise: set
_UpdateDateand_UpdateUser, and copy the compositeNamevalue into the discrete_FirstName,_LastName,_MiddleInitial, and_PreferredName/_NameExtensionfields. - If the existing
ContactNoteis not new and itsNoteTextis blank, callContactNote.Delete()so the note row is removed rather than stored empty (BR-009). - Persist the root row.
- Insert:
BeforeInsertbinds all rootPERSONfields (SQL-003, PERSON).- On failure (rows affected ≠ 1): add the
InsertErrorbroken rule.
- On failure (rows affected ≠ 1): add the
- Update:
BeforeUpdatere-binds the same field set (SQL-066, PERSON).- On failure (rows affected ≠ 1): add the
UpdateErrorbroken rule with the DB-contention message.
- On failure (rows affected ≠ 1): add the
- Validate before delete (BR-003).
ValidateOnDelete = TrueregistersValidateNoRelationshipsOnDelete, which returnsTrueunlessIsDeleted, in which case it returnsMe.CustomerRelationships.Count = 0. CustomerRelationshipsinvokesGetRelationships(), which queriesCOMPANY_PERSONjoined toCOMPANYfor the contact's identifier (SQL-028, COMPANY_PERSON, COMPANY) and materializes aclsCustomerRelationshipper row, resolving the relationship description throughGetRelationshipCode(dpi).- On failure: the broken rule blocks the delete before
BeforeDeleteruns. - Delete the root row. Once the relationship rule passes,
BeforeDeleteissuesDelete PERSON(SQL-077, PERSON). - On failure (rows affected ≠ 1): add the
DeleteErrorbroken rule. - Save children after the root row (
OnSaveComplete). In this fixed order:Addresses,Emails,Phones, thenContactNote(see Address info CRUD and linking, Email info CRUD and linking, Phone info CRUD and linking, Long-note persistence).
Process flow¶
The following diagram visualizes the contact save/delete cascade, including the relationship-based delete guard.
flowchart TD
Start([Caller calls Save]) --> Deleted{IsDeleted?}
Deleted -->|Yes| RelRule{CustomerRelationships<br>count = 0?}
RelRule -->|No| Blocked([Broken rule:<br>cannot delete while related])
RelRule -->|Yes| ClearChildren[Clear Emails, Phones,<br>Addresses; delete ContactNote;<br>save those four now]
ClearChildren --> DeleteRoot[Delete PERSON row]
DeleteRoot --> DelCheck{RowsAffected = 1?}
DelCheck -->|No| DelError([DeleteError added])
DelCheck -->|Yes| DoneDel([Contact removed])
Deleted -->|No| SetAudit[Set UpdateDate, UpdateUser;<br>copy Name into parts]
SetAudit --> NoteCheck{ContactNote not new<br>and NoteText blank?}
NoteCheck -->|Yes| DeleteNote[Delete ContactNote row]
NoteCheck -->|No| RootSave
DeleteNote --> RootSave[Insert or update PERSON row]
RootSave --> RowCheck{RowsAffected = 1?}
RowCheck -->|No| Error([InsertError / UpdateError added])
RowCheck -->|Yes| Cascade[OnSaveComplete:<br>save Addresses, Emails,<br>Phones, ContactNote]
Cascade --> Done([Save complete])
Failure and edge-case behavior¶
- Deletion is blocked outright while any
COMPANY_PERSONrelationship row still references the contact; the caller must remove or reassign relationships first (BR-003). - The delete branch of
OnSavereturns immediately after saving the cleared child collections, skipping the composite-name copy and note-blank check performed on the non-delete path; those steps are not needed once the contact is being removed. - Removing an item from
Addresses,Emails, orPhonesdeletes only the link row (PERSON_ADDRESS,PERSON_EMAIL,PERSON_PHONE); the standalone info row is not deleted as part of this cascade. - A blank long note is deleted rather than saved as an empty string (BR-009).
Related documentation¶
Sources¶
Business\SAIF.Business.CustomerContact\Contact\clsContact.vb