---
title: User access group management
summary: Loads and persists access-group selections for a user type, including Active Directory reconciliation helpers.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.External.Users
reference_type: package
operation_id: OP-EU-05
trigger: Caller loads possible access groups, reconciles AD groups, or saves selected access-group rows.
---

# User access group management

This operation governs how a user type sees and saves `EXT_USER_ACCESS_GROUP` rows. It combines the static access-group definitions from code tables with the selected rows for a specific user type, then provides helper queries to add or remove AD-backed assignments.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-EU-05 |
| **Trigger** | User-type maintenance loads access groups or saves selected group changes |
| **Inputs** | User-type ID, user-type code, access type, culture code, AD group list, selected flags |
| **Outputs** | Possible access-group collection, AD-derived additions/removals, persisted `EXT_USER_ACCESS_GROUP` rows |
| **Primary components** | `clsUserAccessGroupCollection.GetAllUserTypeAccessGroups`, `clsUserAccessGroupCollection.GetAllPossibleUserAccessGroups`, `clsUserAccessGroupCollection.OnSave` |
| **Entry point** | `Business\SAIF.Business.External.Users\clsUserAccessGroup.vb` |

## Steps

1. **Load the static access-group definitions for a user type.** `GetAllUserTypeAccessGroups` reads the allowed group definitions from `EXT_USER_TYPE_ACCESS_GROUP`, joining access-group code text and applying access-type filtering for registration, SAIF employee, or steward views ([SQL-023](../sql/SQL-023-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP_CODE](../database-tables.md#ext_user_access_group_code)).
2. **Load the full selectable matrix for one user type.** `GetAllPossibleUserAccessGroups` left-joins the user's actual `EXT_USER_ACCESS_GROUP` rows onto the allowed definition set so each group comes back either selected or available ([SQL-024](../sql/SQL-024-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP](../database-tables.md#ext_user_access_group), [EXT_USER_ACCESS_GROUP_CODE](../database-tables.md#ext_user_access_group_code)).
3. **Load one persisted access-group row when direct retrieval is needed.** `clsUserAccessGroup` retrieves a single row by sequence number and joins back to the user type and definition tables ([SQL-022](../sql/SQL-022-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP](../database-tables.md#ext_user_access_group)).
4. **Load AD group mappings for a selected access group.** The AD-group helper query resolves the AD group names associated with an access-group code ([SQL-021](../sql/SQL-021-saif-business-external-users.md), [EXT_USER_AD_GROUP](../database-tables.md#ext_user_ad_group), [EXT_USER_ACCESS_GROUP_AD](../database-tables.md#ext_user_access_group_ad)).
5. **Find access groups that AD just added.** `GetAllUnassignedUserAccessGroups` loops the supplied AD group names and, for each one, finds mapped access groups that are allowed for the user type but not yet assigned to the user ([SQL-025](../sql/SQL-025-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP_AD](../database-tables.md#ext_user_access_group_ad), [EXT_USER_ACCESS_GROUP](../database-tables.md#ext_user_access_group)).
6. **Find access groups that AD effectively removed.** `GetAllRemovedUserAccessGroups` builds a `NOT IN` list from the supplied AD groups, finds assigned groups whose AD mapping is now absent, and maps those IDs back to the possible-group collection ([SQL-026](../sql/SQL-026-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP](../database-tables.md#ext_user_access_group), [EXT_USER_ACCESS_GROUP_AD](../database-tables.md#ext_user_access_group_ad), [EXT_USER_AD_GROUP](../database-tables.md#ext_user_ad_group)).
7. **Track selection changes instead of full-row edits.** `clsUserAccessGroup.IsDirty` is driven by the `Selected` tracked property, so the collection save logic only reacts to selection flips.
8. **Translate selection changes into inserts and deletes.** `clsUserAccessGroupCollection.OnSave` walks the collection before the base save.
   - If `Selected` changed from `False` to `True` and no persisted row exists yet, it assigns a new `EXT_USER_ACCESS_GROUP_SEQ` value, fills `UserTypeID`, and prepares an insert ([SQL-006](../sql/SQL-006-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP](../database-tables.md#ext_user_access_group)).
   - If `Selected` changed from `True` to `False` and a persisted row exists, it queues the row for removal and the base save issues the delete ([SQL-053](../sql/SQL-053-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP](../database-tables.md#ext_user_access_group)).
   - If a persisted row remains selected but its metadata changed, it updates the row under `UPDATE_NUM` concurrency control ([BR-EU-05](../business-rules.md#br-eu-05-optimistic-concurrency-via-update_num), [SQL-047](../sql/SQL-047-saif-business-external-users.md), [EXT_USER_ACCESS_GROUP](../database-tables.md#ext_user_access_group)).
9. **Validate access-group combinations through the owning user.** `clsUser.ValidateAccessGroupsRequired` enforces the user-type-specific selection rules before save.
   - Agency users must choose exactly one agency-style group and cannot mix agency with producer or producer with commission statements.
   - Policyholders must choose at least one self-registration group, and claim-notes access requires view-policy-and-claim-information access.
   - Medical providers must choose at least one non-system group, cannot mix bill-search with either claim-profile access, and cannot choose bill-search-claim-profile without claim-profile.

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
    Start([Access-group load or save]) --> Load[Load definitions and current selections]
    Load --> AD{Reconcile AD groups?}
    AD -->|Yes| Compare[Find added and removed mappings]
    AD -->|No| Select[User edits selections]
    Compare --> Select
    Select --> Validate[Run user-type-specific group validation]
    Validate --> Save[Insert, update, or delete rows based on selection changes]
    Save --> Done([Return saved access-group state])
```

## Failure and edge-case behavior

- Registration and import flows can bypass some access-group validation paths because the owning user object explicitly disables them for those access types.
- Agency and medical-provider combinations fail validation before any database write occurs.
- The AD-removal helper builds a dynamic `NOT IN` clause from the current AD groups, so an empty or malformed group list changes the removal result set.
- Persisted group updates fail with DB-contention messaging when `UPDATE_NUM` no longer matches the stored row ([BR-EU-05](../business-rules.md#br-eu-05-optimistic-concurrency-via-update_num)).

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [SQL catalog](../sql/index.md)
- [Database tables](../database-tables.md)

## Sources
- `Business\SAIF.Business.External.Users\clsUser.vb`
- `Business\SAIF.Business.External.Users\clsUserAccessGroup.vb`
