# SaveAdHocFieldDefinitionStaff - Advanced Documentation ## Overview Creates or updates a single staff ad hoc field definition, including its dropdown options and role-based view/edit permissions. The caller passes one field definition object per call. This is a long-running operation - it declares a `[LongRunningId] string ProcessID` parameter and is invoked through `ProcessStatus.Run`. This operation performs no dependent widget or form regeneration of any kind - the field is validated, saved, committed, and the cache is cleared. `IsSynced` is not used for this ad hoc field type and is ignored - it is never read or set anywhere in the method. ## Business Rules - The field definition is validated before any write: field label is required, must be a valid field name (cannot contain `,`, `"`, `#`), data type is required, and category is required (`ValidateFieldDefinition`) - The field label must be unique for the company within `vwAdHocFieldDefinitionsStaff` only - There is no automatic sort order assignment for new fields in this method - `SortOrder` is taken directly from the incoming `Item.SortOrder` - Dropdown options, edit roles, and view roles are synced as Aptify sub-types (`AdHocFieldDefinitionsStaffDropdownOptions`, `AdHocFieldDefinitionsStaffEditRoles`, `AdHocFieldDefinitionsStaffViewRoles`) - existing rows not present in the incoming lists are removed, and missing rows are added - There is no `UsedBy` concept in this method - `Item.UsedBy` is never read, and there is no `AdHocFieldDefinitionsStaffUsedBy` sub-type synced - There is no pre-save widget-field recalculation step for this operation - There is no post-commit dependent-widget or form regeneration step - `UpdateFieldForDependentWidgets` is never called and no other widget search runs - The ad hoc field cache is cleared exactly once, after the transaction commits, via `ClearAdHocCache(AdHocFieldType.Staff, CompanyID)`, which in turn clears the staff ad hoc field definitions cache for the firm and the contact search field cache for the firm ## Permissions & Security - Caller must be logged in (`curPerson.ID >= 1`) - Caller must have a company selected (`curCompany.ID >= 1`) - Caller must be a member of the current company (`IsPartOfCompany` check) - Caller must have the **ManageFirm** permission, checked inside the controller method (`Controllers.Role.Permissions.ManageFirm`) - The saved field definition is always scoped to the caller's current company (`CompanyID` is set from `_UnitOfWork.CurrentCompany.ID`, not from the incoming `Item`) ## Data Flow 1. Validate authentication, company membership, and ManageFirm permission (in the service method, before the controller is invoked) 2. Check ManageFirm permission again inside the controller method 3. Validate the field definition shape (`ValidateFieldDefinition`) 4. Report progress via `ProcessStatus.SetProcessStatus(ProcessID, "Saving Field [{Item.FieldLabel}]")` 5. Query `vwAdHocFieldDefinitionsStaff` for an existing field label match for this company (excluding the current record's ID); throw if found 6. Get (or create) the `AdHocFieldDefinitionsStaff` Aptify entity object for `Item.ID` and set its scalar fields (`CompanyID`, `CategoryID`, `FieldLabel`, `DataType`, `SortOrder`, `Description`, `IsReadOnly`, `Format`) 7. Sync the edit role, view role, and dropdown option sub-types 8. Begin transaction 9. Save the entity object; throw if the save fails 10. Commit transaction 11. Set `Item.ID` to the saved record's ID 12. Clear the ad hoc field cache for `Staff` fields 13. Return the saved `Item` ## Side Effects - **Database write**: The `AdHocFieldDefinitionsStaff` entity record is created or updated via Aptify - **Database writes**: Dropdown option, edit role, and view role sub-type rows are synced (added/removed) to match the incoming definition - **Cache invalidation**: The staff ad hoc field definitions cache for the firm is cleared once, after commit - **Cache invalidation**: The contact search field cache for the firm is cleared once, as part of the same `ClearAdHocCache` call ## Error Conditions - `RadoloException("You must be logged in to perform this action.")` - caller not authenticated - `RadoloException("You must have a company selected to perform this action.")` - no company context - `RadoloException("You can only perform this action for a company you are a member of.")` - cross-company attempt - `RadoloException("You do not have permission to save staff adhoc fields. Please contact your administrator.")` - missing ManageFirm permission - `RadoloException("Field Label is required.")` - missing field label - `RadoloException("Field Label can not contain ',\",#.")` - invalid field label characters - `RadoloException("Data Type is required.")` - missing data type - `RadoloException("Category is required.")` - missing or invalid category - `RadoloException("An ad hoc field with this field label[{FieldLabel}] already exists.")` - duplicate field label within staff ad hoc fields - `RadoloException("There was a problem saving this ad hoc field definition : {error}")` - Aptify entity save failed - There is no catch block anywhere in this method - any exception from validation, the duplicate check, or the save propagates unmodified to the caller ## Usage Notes - This is a long-running operation wrapped in `ProcessStatus.Run` with a `[LongRunningId]` `ProcessID` parameter, and the controller method does call `ProcessStatus.SetProcessStatus` once, before the duplicate-label check, to report "Saving Field [{Item.FieldLabel}]" - This operation has no dependent-widget regeneration step at all, so cost does not scale with anything - there is nothing for `UsedBy` to drive, and `Item.UsedBy` is not read - Because there is no catch block, a failure at any stage (validation, duplicate check, or save) always surfaces to the caller as an exception - there is no scenario where this call reports success while a downstream step silently failed - The incoming `Item.CompanyID` is not trusted; the field is always saved under the caller's current company