# SaveContactCompany - Advanced Documentation ## Overview Creates or updates a company/organization contact with validation of required Name field, phone/email formats, and preferred contact preferences. Verifies phone numbers via SMS integration if configured, uploads photos to S3, and syncs nested addresses, phones, emails, notes, and custom fields. Triggers JSON re-serialization for associated claims. ## Business Rules - The SecureApi layer forces `IsPerson = false` on the contact before passing it to `Contact.Save`, ensuring the correct validation path is used regardless of what the caller sends. - **Phone number normalization**: Same as SaveContactPerson -- legacy phone fields are shoehorned into `FullNumber` for backward API compatibility. - **Email normalization**: Same as SaveContactPerson -- cleaned and trimmed of special characters. - **Company-specific validation**: - Name is required (company contacts use Name directly, not FirstName/LastName). - The `IsPerson` flag must be false; if true, an error is returned. - **Shared contact validation** (same as SaveContactPerson): - Preferred contact method must match available contact data. - Maximum 15 addresses, 15 email addresses, and 15 phone numbers. - Addresses must have a Type if any fields are populated. - AddressLine1 rejects placeholder values. - Email and phone format validation. - **Address filtering**: Completely empty addresses are silently dropped. - **Preferred item enforcement**: Same rules as SaveContactPerson -- one preferred per category, defaults to first item. - **Phone verification**: Same SMS integration verification as SaveContactPerson. - **Photo upload**: Same S3 upload behavior as SaveContactPerson. - **Digital signature/initials**: Same file handling as SaveContactPerson. - **Name handling**: Unlike person contacts, company contacts pass the Name through directly (it is NOT nulled for regeneration). - **Firm ownership**: Same rule -- existing contacts must belong to the current firm. - **Timestamps**: Same behavior as SaveContactPerson. - **Post-save**: `CreateEntityFinishContactRun` triggers JSON re-serialization for associated claims. - **No duplicate detection**: Unlike SaveContactPerson, the SecureApi does NOT catch unique index violations with a friendly message. Duplicate company contacts will produce a raw error. ## Permissions & Security - User must be logged in (checked at API layer). - User must have a company selected and be a member of that company (checked at API layer via `IsPartOfCompany`). - No specific named permission is required -- any authenticated firm member can save company contacts. - All referenced ad hoc field staff IDs are validated as firm members via `ValidatePersonWasPartOfFirm`. - Existing contacts must belong to the current firm. ## Data Flow 1. SecureApi validates authentication, company membership, sets `IsPerson = false`, then calls `Contact.Save`. 2. Legacy phone fields normalized. 3. Email addresses cleaned. 4. SMS integration info retrieved. 5. `ValidateCompany` checks IsPerson flag (must be false), required Name field, preferred contact method consistency, collection limits, format validation. 6. Empty addresses filtered out. 7. Ad hoc field staff IDs validated against the firm. 8. Entity loaded; firm ownership verified for existing contacts. 9. Preferred item enforcement applied. 10. Phone numbers verified via SMS integration. 11. SSN cleaned (applicable to company contacts for TaxNumber-related cleanup). 12. All scalar fields written to the entity. 13. Name passed through directly (not nulled like person contacts). 14. Photo uploaded to S3 if base64 data provided. 15. Digital signature and initials updated if provided. 16. Tags, addresses, phones, emails, notes, and ad hoc fields synced via sub-entities. 17. Timestamps set. 18. Transaction started. 19. Entity saved. 20. `CreateEntityFinishContactRun` triggers downstream processing. 21. Transaction committed. 22. Old file cleanup (digital signature/initials). 23. Primary address, email, and phone populated. 24. Contact returned. ## Side Effects - **Database writes**: BaboContacts record with all sub-entities (addresses, phones, emails, notes, tags, ad hoc fields). - **AWS S3**: Photo uploaded for new/changed photos. Old digital files deleted after save. - **SMS/Twilio**: Phone numbers verified via SMS integration. - **JSON re-serialization**: Triggered for associated claims via `CreateEntityFinishContactRun`. - **File cleanup**: Old digital signature/initial files deleted post-commit. ## Error Conditions - `RadoloException("You must be logged in...")` if not authenticated. - `RadoloException("You must have a company selected...")` if no company context. - `RadoloException("You can only perform this action for a company you are a member of.")` if user is not a firm member. - `ArgumentNullException` if Contact is null. - `RadoloException` from `ValidateCompany` for: IsPerson being true, missing Name, preferred contact method mismatch, collection limits, format errors. - `RadoloException("You cannot change firms on a client...")` if FirmID mismatch. - Entity save failures translated via `ThrowNiceErrorForSave`. - `ValidatePersonWasPartOfFirm` throws for invalid ad hoc field staff. - Unlike SaveContactPerson, there is no friendly duplicate detection -- SQL index violations produce raw error messages. ## Usage Notes - `SaveContactCompany` and `SaveContactPerson` are thin wrappers around the same `Contact.Save` method. The only differences are: 1. `IsPerson` is forced to `false` (vs `true` for person). 2. The `ValidateCompany` path is triggered (requires Name instead of FirstName/LastName). 3. No duplicate detection catch block at the SecureApi layer. 4. The Name field is passed through directly instead of being nulled for regeneration. - All sub-entity syncing (addresses, phones, emails) uses `Type` as the key field, so each Type can only appear once. - The same concurrent phone verification behavior applies as SaveContactPerson. - Company contacts can have the same fields as person contacts (DateOfBirth, SocialSecurityNumber, etc.) -- the entity structure is shared. However, the validation only requires Name for company contacts. - The `ContactID` is attached to the exception data at the SecureApi layer for debugging.