# SaveAdHocFieldDefinitionLedgerType - Advanced Documentation ## Overview Creates or updates a single ledger type ad hoc field definition, including its dropdown options, role-based view/edit permissions, and the list of ledger types the field applies to. 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`. The definition is saved and committed first; a dependent widget/form update then runs synchronously before the call returns, but any failure during that dependent-widget work is logged and swallowed rather than surfaced to the caller. `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 `vwAdHocFieldDefinitionsLedgerTypes` only; there is no union check against any system ledger field table - New field definitions (`AdHocFieldDefinition.ID < 1`) are assigned the next sort order, computed from the count of existing field-label rows found during the uniqueness check - Dropdown options, edit roles, view roles, and the used-by ledger type list are synced as Aptify sub-types (`AdHocFieldDefinitionsLedgerTypeDropdownOptions`, `AdHocFieldDefinitionsLedgerTypeEditRoles`, `AdHocFieldDefinitionsLedgerTypeViewRoles`, `AdHocFieldDefinitionsLedgerTypeUsedBy`) - existing rows not present in the incoming lists are removed, and missing rows are added - There is no pre-save widget-field recalculation step for this operation. - The field definition is saved and the transaction is committed BEFORE any dependent widget/form work begins. Re-reading the definition immediately after the call confirms the write even while dependent updates are still running - After commit, the operation calls `UpdateFieldForDependentWidgets`, which searches `vwClaimTypeMenus`/`vwClaimTypeMenuWidgets`/`vwSystemWidgets` across the whole company for any widget whose `ClaimDesignerOptionsJson` references this field's `AdHocFieldID`, updates each matching widget's option JSON through its options handler, and regenerates the claim type's generated forms once per distinct claim type found this way - This search is driven entirely by a company-wide `AdHocFieldID` pattern match against widget JSON, not by `AdHocFieldDefinition.UsedBy` - `UsedBy` is only persisted as the `AdHocFieldDefinitionsLedgerTypeUsedBy` sub-type and never read back inside `UpdateFieldForDependentWidgets` - This dependent-widget search runs against a brand-new `UnitOfWork(new RadoloApplication())`, not the same unit of work used for the save - `UpdateFieldForDependentWidgets` wraps its work in a try/catch that calls `ex.PublishException()` and does not rethrow, so a failure in the widget/form regeneration step does not fail the overall save - the caller receives a successful response even if regeneration failed - The ad hoc field cache for ledger type fields is cleared exactly once, after the dependent-widget work completes (`ClearAdHocCache(AdHocFieldType.ClaimLedger, CompanyID)`) - there is no separate cache clear before regeneration begins ## 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 `AdHocFieldDefinition`) ## 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. Query `vwAdHocFieldDefinitionsLedgerTypes` for an existing field label match for this company; throw if found 4. Get (or create) the `AdHocFieldDefinitionsLedgerTypes` Aptify entity object for `AdHocFieldDefinition.ID` and set its scalar fields (`CompanyID`, `FieldLabel`, `DataType`, `CategoryID`, `Format`, `Description`, `IsReadOnly`) 5. If this is a new field (`ID < 1`), assign the computed sort order 6. Sync the dropdown options, edit roles, view roles, and used-by ledger type sub-types 7. Begin transaction 8. Save the entity object; throw if the save fails 9. Commit transaction 10. Set `AdHocFieldDefinition.ID` to the saved record's ID 11. Call `UpdateFieldForDependentWidgets`: find every claim type menu widget across the company whose options JSON references this field, update each widget's option string, and regenerate generated forms for each distinct claim type found - any exception here is published, not rethrown 12. Clear the ad hoc field cache for `ClaimLedger` fields 13. Return the saved `AdHocFieldDefinition` ## Side Effects - **Database write**: The `AdHocFieldDefinitionsLedgerTypes` entity record is created or updated via Aptify - **Database writes**: Dropdown option, edit role, view role, and used-by ledger type sub-type rows are synced (added/removed) to match the incoming definition - **Database writes (best-effort)**: `ClaimTypeMenuWidgets` option JSON is updated for any widget across the company that references this field's `AdHocFieldID` - a failure here is published but does not undo the save - **Database updates (best-effort)**: Claim type generated forms (HTML) are regenerated once for each distinct claim type whose widgets reference this field - **Cache invalidation**: Ad hoc field cache for `ClaimLedger` fields is cleared once, after the dependent-widget work completes ## 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 ledger type 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 ledger type ad hoc fields - `RadoloException("There was a problem saving this ad hoc field definition : {error}")` - Aptify entity save failed - A failure while updating dependent widgets or regenerating claim type forms does NOT raise an exception to the caller - it is caught, published via `ex.PublishException()`, and swallowed ## Usage Notes - This is a long-running operation wrapped in `ProcessStatus.Run` with a `[LongRunningId]` `ProcessID` parameter, but the controller method itself does not call `ProcessStatus.SetProcessStatus` at any point and has no `finally` block - progress is not reported mid-operation. - Regeneration cost for this operation does NOT scale with the size of `AdHocFieldDefinition.UsedBy`. `UsedBy` (ledger type IDs) is only persisted as a sub-type describing which ledger types the field applies to. The actual regeneration work is driven by a company-wide search for claim type menu widgets whose options JSON references this field, so cost scales with the number of matching widgets/claim types found by that search, independent of `UsedBy`. - Because a regeneration failure is caught and published rather than rethrown, a caller cannot tell from the response alone whether dependent widget/form regeneration succeeded; the saved field definition itself is reliable, but downstream generated forms may be stale if that step failed. - Because the save and commit happen before the dependent-widget work starts, callers can re-read the field definition and see the persisted values while that work is still in progress. - The incoming `AdHocFieldDefinition.CompanyID` is not trusted; the field is always saved under the caller's current company.