# SavePerson - Advanced Documentation ## Overview Creates or updates a staff member's profile including contact info, digital signature/initial assets, and ad-hoc custom fields. Requires ManageUsers permission or self-edit. Validates email uniqueness, manages Web User and Person records atomically, uploads photos to S3, and clears security caches. ## Business Rules - Users can edit their own profile without ManageUsers permission; editing another person requires ManageUsers - Email must pass `IsEmail()` validation - Email is used as the Web User's UserID (login identifier) and must be unique across the system (enforced by `UQ_WebUser_UserID` constraint) - New Web User records are automatically enabled and stamped with a CreatedDate - New Person records are stamped with a CreatedDate - Profile photos are uploaded to S3 only for existing persons (ID > 0); new persons cannot have a photo on creation - Photos are stored at the path `{_PersonPhotoPrefix}/{GlobalID}` in S3 - If the photo field contains a `Person.photoredirect` URL (existing photo reference), it is not re-uploaded - Digital signature and digital initial are managed as embedded Aptify objects on the Person entity - If signature/initial `StorageName` is null, the reference is cleared (set to null) - Phone, fax, and address fields are cleared (set to null parent ID) when all component fields are blank - Ad hoc fields are validated for company ownership before saving - Ad hoc fields the person cannot view are excluded from modification - If the Person entity is dirty, a Timestamp is set on both Person and Web User - The person is associated with the company via `AddCompany`, which creates a `PersonCompanies` subtype record and logs a `CompanyPersonEvents` "Added" event - Password is saved via `IPasswordResetPerson.SavePassword` only if a non-empty password is provided - Password is cleared from the return object after save - Security info cache for the entire firm is cleared after save - Person cache is always cleared in the `finally` block ## Permissions & Security - Caller must be logged in (`curPerson.ID >= 1`) - Caller must have a company selected (`curCompany.ID >= 1`) - The target person must belong to the current company (`IsPartOfCompany` check at the API layer) - Caller must have **ManageUsers** permission OR be editing their own record (`Person.ID == CurrentPerson.ID`) ## Data Flow 1. Validate ManageUsers permission or self-edit 2. Set process status to "Saving Person" 3. Load Web User and Person Aptify entities 4. Validate email format 5. Set name, contact, and professional fields on both entities 6. If photo data is base64-encoded (not a redirect URL) and person exists: upload to S3, update Photo field 7. Set phone, fax, and address fields (or clear parent IDs if all blank) 8. Set or clear digital signature and digital initial embedded objects 9. Determine ad hoc fields to save and remove via `GetAdHocsToModify` 10. Begin transaction 11. Delete removed ad hoc fields 12. Save Person entity (with FirmID add-value for webhook support) 13. Save new/modified ad hoc fields with PersonID and CompanyID 14. Save Web User entity (with LinkID pointing to Person) 15. Save password if provided 16. Call `AddCompany` to ensure person-company association and log event 17. Commit transaction 18. Clear security info cache for the firm 19. Clear person cache (in `finally` block) 20. Return updated PersonInfo with password cleared ## Side Effects - **S3 upload**: Profile photo uploaded to AWS S3 if changed - **Database writes**: Person and Web User records created or updated atomically - **Database writes**: Ad hoc field values created, updated, or deleted - **Database writes**: PersonCompanies association record created if new - **Database writes**: CompanyPersonEvents log entry created ("Added" event) - **Password change**: Web User password updated if provided - **Cache invalidation**: Person cache cleared via `ClearPersonCache` - **Cache invalidation**: Claim security info cache cleared for the entire firm via `ClearSecurityInfoCacheForFirm` ## Error Conditions - `RadoloException("You must be logged in to perform this action.")` - not authenticated - `RadoloException("You must have a company selected to perform this action.")` - no company context - `RadoloException("You can only update the profile of someone in your company.")` - target not in company - `RadoloException("You do not have permission to edit a user...")` - missing ManageUsers and not self-edit (includes CurrentPersonId, PersonId, CompanyID in exception data) - `RadoloException("The email provided is not a valid email address : [{email}].")` - invalid email format - `RadoloException("There was a problem saving this profile: {error}")` - Person entity save failed - `RadoloException("The Email you have chosen, [{email}], is already in use...")` - duplicate email (unique constraint violation) - `RadoloException("There was a problem saving this profile: {error}")` - Web User entity save failed (non-duplicate error) - `RadoloException("There was a problem removing an adhoc field from this staff: {error}")` - ad hoc delete failed - `RadoloException("There was a problem saving an adhoc field from this staff: {error}")` - ad hoc save failed ## Usage Notes - This endpoint is async and uses `ProcessStatus.RunAsync` for progress tracking via CreateRequestID. - The Person and Web User are saved as separate Aptify entities within a single transaction. The Web User's LinkID is set to the Person's RecordID after the Person is saved. - For new persons, the photo cannot be uploaded during creation (the Person must exist first to have a GlobalID). Upload the photo on a subsequent save. - The `AddCompany` call is idempotent for the association (uses `Find` then `Add` if not found), but always logs a new CompanyPersonEvents record. - Ad hoc fields that the person is not allowed to view are excluded from modification, preventing privilege escalation through the ad hoc field system. - The `finally` block ensures person cache is always cleared, even on failure.