# SaveAdHocFieldDefinitionClaimList - Advanced Documentation ## Overview Creates or updates a single ad hoc field definition on a claim 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`. After the field definition is saved and committed, the operation synchronously searches for and regenerates any claim type widgets that reference this field before returning. `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 `ClaimListTypes` 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 (`ClaimListTypeFieldEditRoles`, `ClaimListTypeFieldViewRoles`), 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 `ClaimListTypeFieldDropdownOptions` sub-type from `Item.DropdownOptions`, projected into `Name`/`Sequence` pairs (`Sequence` is the 1-based position in the incoming list); no explicit key property is passed to the sync call, so the sub-type's own designated name field is used to match existing rows - `Item.UsedBy` exists on the DTO but is neither read nor persisted anywhere in this operation - After the field is saved and the transaction is committed, the field is converted to a `ListItemFieldInfo` and passed to `UpdateFieldForDependentWidgets`, unconditionally, with no gating check - `UpdateFieldForDependentWidgets` searches claim type menu widgets whose `ClaimDesignerOptionsJson` references this field's `AdHocFieldID` and, for each match, updates the widget's options JSON and regenerates that claim type's generated forms; this call runs synchronously as part of the request, not on a background task - `UpdateFieldForDependentWidgets` wraps its work in a try/catch that calls `ex.PublishException()` and does not rethrow, so any failure during widget lookup or form regeneration does not propagate to the caller and the save is still reported as successful - Two separate cache-clearing calls run after the regeneration step: `ClearAdHocFieldDefinitionsForList(AdHocFieldType.ClaimList, CompanyID)` removes the cached list of field definitions for this claim list type plus each field's cached dropdown options; `ClearAdHocCache(AdHocFieldType.ClaimList, CompanyID)` for `AdHocFieldType.ClaimList` 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 no progress messages of its own; `ProcessStatus.SetProcessStatus` is never called anywhere in this method ## 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. Get the `ClaimListTypes` entity object for `ListID` and confirm its `CompanyID` matches the caller's current company; throw if it does not 4. Find the existing `ClaimListTypeFields` sub-type row for `Item.ID`, or add a new one if not found 5. Set the field's scalar values (`CategoryID`, `FieldLabel`, `DataType`, `SortOrder`, `Description`, `IsReadOnly`, `Format`) 6. Sync the edit role, view role, and dropdown option sub-types 7. Begin transaction 8. Save the `ClaimListTypes` entity object; if the save fails, throw a duplicate-specific error when `ge.LastError` contains "duplicate", otherwise throw a generic save-failure error 9. Commit transaction 10. Set `Item.ID` to the saved field record's ID 11. Convert `Item` to a `ListItemFieldInfo` and call `UpdateFieldForDependentWidgets` to regenerate any claim type widgets referencing this field 12. Clear the per-list field definition cache and the contact search field cache 13. Return the saved `Item` ## Side Effects - **Database write**: A `ClaimListTypeFields` sub-type row under the `ClaimListTypes` 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 - **Database writes**: Any `ClaimTypeMenuWidgets` records whose options JSON references this field's `AdHocFieldID` are updated with regenerated options JSON, and the corresponding claim type's generated forms are rebuilt - **Cache invalidation**: The cached field definition list (and per-field dropdown option caches) for this claim 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 {ClaimLabel} List 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 before the widget regeneration step propagates to the caller unmodified. A failure inside the widget regeneration step is published and swallowed, so it never reaches the caller ## Usage Notes - This is a long-running operation wrapped in `ProcessStatus.Run` with a `[LongRunningId]` `ProcessID` parameter, but it reports no progress messages of its own; the `ProcessID` is forwarded only into `UpdateFieldForDependentWidgets`, which passes it through to claim type form regeneration - A caller receiving a successful response cannot assume that dependent claim type widgets were actually updated, because a regeneration failure is published to the exception log and swallowed rather than surfaced - `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 claim list ad hoc fields