# SaveAdHocFieldDefinitionContactList - Advanced Documentation ## Overview Creates or updates a single ad hoc field definition on a contact list, including its edit roles, view roles, and dropdown options. The caller passes one field definition object plus the target `ListID` per call. This is a long-running operation - it declares a `[LongRunningId] string ProcessID` parameter and is invoked through `ProcessStatus.Run`. The definition is saved and committed and the operation returns; there is no dependent widget or generated-form regeneration step of any kind. `IsSynced` is not used for this ad hoc field type and is ignored. ## 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 target list must belong to the caller's current company; the list's `CompanyID` is read from the `ContactListTypes` entity and compared against `_UnitOfWork.CurrentCompany.ID` - There is no pre-save query against any view to check for a duplicate field label; instead, a duplicate is detected only when the entity save fails and `ge.LastError` contains the substring "duplicate" - `SortOrder` is taken directly from `Item.SortOrder` as supplied by the caller; this operation does not compute or assign a sort order for new fields - Edit roles and view roles are synced as Aptify sub-types (`ContactListTypeFieldEditRoles`, `ContactListTypeFieldViewRoles`), each keyed on `RoleID` - existing rows not present in the incoming lists are removed, and missing rows are added - Dropdown options are synced as the `ContactListTypeFieldDropdownOptions` sub-type from `Item.DropdownOptions`, projected into `Name`/`Sequence` pairs (`Sequence` is the 1-based position in the incoming list), keyed explicitly on `Name` - `Item.UsedBy` exists on the DTO but is neither read nor persisted anywhere in this operation - There is no dependent-widget search and no generated-form regeneration step for this operation at all - the method ends after the save, commit, and cache clear - Two separate cache-clearing calls run after commit: `ClearAdHocFieldDefinitionsForList(AdHocFieldType.ContactList, CompanyID)` removes the cached list of field definitions for this contact list type plus each field's cached dropdown options; `ClearAdHocCache(AdHocFieldType.ContactList, CompanyID)` for `AdHocFieldType.ContactList` does not clear a firm-level ad hoc field cache (that branch only runs for field types other than `ClaimList`/`ContactList`) - it clears the contact search field cache via `Contact.ClearAllSearchFieldCache` - The operation reports a single progress message (`ProcessStatus.SetProcessStatus(ProcessID, "Saving Field [{FieldLabel}]")`) immediately after validation, before the company-ownership check runs; no further progress is reported afterward ## 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 target list is confirmed to belong to the caller's current company before any field is written; a list from another company is rejected ## Data Flow 1. Validate authentication, company membership, and ManageFirm permission (in the service method, before the controller is invoked) 2. Validate the field definition shape (`ValidateFieldDefinition`) 3. Report progress: "Saving Field [{FieldLabel}]" 4. Get the `ContactListTypes` entity object for `ListID` and confirm its `CompanyID` matches the caller's current company; throw if it does not 5. Find the existing `ContactListTypeFields` sub-type row for `Item.ID`, or add a new one if not found 6. Set the field's scalar values (`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 `ContactListTypes` entity object; if the save fails, throw a duplicate-specific error when `ge.LastError` contains "duplicate", otherwise throw a generic save-failure error 10. Commit transaction 11. Set `Item.ID` to the saved field record's ID 12. Clear the per-list field definition cache and the contact search field cache 13. Return the saved `Item` ## Side Effects - **Database write**: A `ContactListTypeFields` sub-type row under the `ContactListTypes` entity for `ListID` is created or updated via Aptify - **Database writes**: Edit role, view role, and dropdown option sub-type rows are synced (added/removed) to match the incoming definition - **Cache invalidation**: The cached field definition list (and per-field dropdown option caches) for this contact list type is cleared - **Cache invalidation**: The contact search field cache for the company is cleared ## 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 person contact 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("You cannot save a category for a list in this company.")` - the target list does not belong to the caller's current company - `RadoloException("There is already an item named {FieldLabel}. Please make sure you are not creating a duplicate.")` - the entity save failed with a duplicate error - `RadoloException("There was a problem saving this field definition : {error}")` - the entity save failed for any other reason - The method has no try/catch of its own; any exception raised during the save propagates to the caller unmodified ## Usage Notes - This is a long-running operation wrapped in `ProcessStatus.Run` with a `[LongRunningId]` `ProcessID` parameter, and it does report one progress message before the company-ownership check - This operation performs no dependent-widget search and no generated-form regeneration of any kind - a caller does not need to account for any background or best-effort work continuing after the response is returned - `Item.UsedBy` may be populated on the incoming DTO but has no effect on this operation - `IsSynced` is not read or set anywhere in this operation and has no effect on contact list ad hoc fields