---
title: "SAIF.Business.Menu business rules"
summary: "Enumerated business rules with per-rule trigger, enforcement, and outcomes."
status: draft
owner: SAIF documentation team
last_reviewed: 2026-08-17
library: SAIF.Business.Menu
reference_type: package
---

# SAIF.Business.Menu business rules

This page captures the business rules enforced directly by `SAIF.Business.Menu`.

## BR-MENU-001: Optimistic contention on write

| Field | Value |
| --- | --- |
| **Statement** | Every insert, update, and delete must affect exactly one row before the object commits its state transition. |
| **Trigger** | A CRUD-capable business object finishes an insert, update, or delete. |
| **Enforcement** | Each entity `AfterInsert`, `AfterUpdate`, and `AfterDelete` checks `RowsAffected <> 1`. |
| **Outcome** | Unexpected write counts surface as contention or insert-failed errors. |
| **Tables** | [BUSONLINE_APPS_CODE](database-tables.md#busonlineappscode)<br>[MENU_CATEGORY_CODE](database-tables.md#menucategorycode)<br>[MENU_ITEM](database-tables.md#menuitem)<br>[MENU_PAGE](database-tables.md#menupage)<br>[MENU_SECURITY](database-tables.md#menusecurity)<br>[MENU_SECURITY_USER](database-tables.md#menusecurityuser)<br>[MENU_FAVORITE](database-tables.md#menufavorite) |
| **SQL** | [SQL-MENU-001](sql/SQL-MENU-001-delete-application-code.md)<br>[SQL-MENU-002](sql/SQL-MENU-002-insert-application-code.md)<br>[SQL-MENU-004](sql/SQL-MENU-004-update-application-code.md)<br>[SQL-MENU-007](sql/SQL-MENU-007-delete-menu-category-code.md)<br>[SQL-MENU-008](sql/SQL-MENU-008-insert-menu-category-code.md)<br>[SQL-MENU-010](sql/SQL-MENU-010-update-menu-category-code.md)<br>[SQL-MENU-013](sql/SQL-MENU-013-delete-menu-item.md)<br>[SQL-MENU-014](sql/SQL-MENU-014-insert-menu-item.md)<br>[SQL-MENU-016](sql/SQL-MENU-016-update-menu-item.md)<br>[SQL-MENU-019](sql/SQL-MENU-019-delete-menu-page.md)<br>[SQL-MENU-020](sql/SQL-MENU-020-insert-menu-page.md)<br>[SQL-MENU-022](sql/SQL-MENU-022-update-menu-page.md)<br>[SQL-MENU-025](sql/SQL-MENU-025-delete-menu-security-row.md)<br>[SQL-MENU-026](sql/SQL-MENU-026-insert-menu-security-row.md)<br>[SQL-MENU-028](sql/SQL-MENU-028-update-menu-security-row.md)<br>[SQL-MENU-031](sql/SQL-MENU-031-delete-menu-security-user-row.md)<br>[SQL-MENU-032](sql/SQL-MENU-032-insert-menu-security-user-row.md)<br>[SQL-MENU-033](sql/SQL-MENU-033-update-menu-security-user-row.md)<br>[SQL-MENU-037](sql/SQL-MENU-037-delete-menu-favorite.md)<br>[SQL-MENU-038](sql/SQL-MENU-038-insert-menu-favorite.md)<br>[SQL-MENU-040](sql/SQL-MENU-040-update-menu-favorite.md) |
| **Source** | `SAIF.Business.Menu\clsApplicationCode.vb`<br>`SAIF.Business.Menu\clsMenuCategoryCode.vb`<br>`SAIF.Business.Menu\clsMenuFavorite.vb`<br>`SAIF.Business.Menu\clsMenuItem.vb`<br>`SAIF.Business.Menu\clsMenuPage.vb`<br>`SAIF.Business.Menu\clsMenuSecurity.vb`<br>`SAIF.Business.Menu\clsMenuSecurityUser.vb` |

---

## BR-MENU-002: Unsecured menu items are visible by default

| Field | Value |
| --- | --- |
| **Statement** | A menu item with no `MENU_SECURITY` row is treated as public and returned with `UnsecuredFlag = 'Y'`. |
| **Trigger** | The authorized-menu query evaluates an item with no security rows. |
| **Enforcement** | `RetrieveSQL` and `RetrieveAllSQL` execute a `NOT EXISTS` branch against [MENU_SECURITY](database-tables.md#menusecurity). |
| **Outcome** | Security restrictions are opt-in; missing security rows leave the item public. |
| **Tables** | [MENU_ITEM](database-tables.md#menuitem)<br>[MENU_SECURITY](database-tables.md#menusecurity) |
| **SQL** | [SQL-MENU-043](sql/SQL-MENU-043-retrieve-authorized-menu-items-for-user.md)<br>[SQL-MENU-044](sql/SQL-MENU-044-retrieve-all-authorized-menu-items.md) |
| **Source** | `SAIF.Business.Menu\clsMenuAuthorizedItemCollection.vb` |

---

## BR-MENU-003: Security-group matching is case-insensitive

| Field | Value |
| --- | --- |
| **Statement** | A secured menu item is authorized only when a user membership row matches an item group name case-insensitively. |
| **Trigger** | The authorized-menu query evaluates an item with assigned security rows. |
| **Enforcement** | The authorized-menu query joins [MENU_SECURITY](database-tables.md#menusecurity) to [MENU_SECURITY_USER](database-tables.md#menusecurityuser) with `upper(USR.SECURITY_GROUP_TXT) = upper(SEC.SECURITY_GROUP_TXT)`. |
| **Outcome** | Group-name casing differences do not block access, but the user must belong to an assigned group. |
| **Tables** | [MENU_SECURITY](database-tables.md#menusecurity)<br>[MENU_SECURITY_USER](database-tables.md#menusecurityuser)<br>[MENU_ITEM](database-tables.md#menuitem) |
| **SQL** | [SQL-MENU-043](sql/SQL-MENU-043-retrieve-authorized-menu-items-for-user.md)<br>[SQL-MENU-044](sql/SQL-MENU-044-retrieve-all-authorized-menu-items.md) |
| **Source** | `SAIF.Business.Menu\clsMenuAuthorizedItemCollection.vb` |

---

## BR-MENU-004: Favorites are always included and ordered

| Field | Value |
| --- | --- |
| **Statement** | A user's active favorites are always added to the authorized-menu result set and the `Favorites` view sorts them by `MenuOrder`. |
| **Trigger** | A user-scoped authorized-menu query executes and the favorites view is exposed. |
| **Enforcement** | `RetrieveSQL` unions active favorites and `Favorites` sorts the in-memory subset. |
| **Outcome** | Favorite items remain visible until soft-deleted and are returned in display order. |
| **Tables** | [MENU_FAVORITE](database-tables.md#menufavorite)<br>[MENU_ITEM](database-tables.md#menuitem)<br>[MENU_PAGE](database-tables.md#menupage) |
| **SQL** | [SQL-MENU-043](sql/SQL-MENU-043-retrieve-authorized-menu-items-for-user.md) |
| **Source** | `SAIF.Business.Menu\clsMenuAuthorizedItemCollection.vb`<br>`SAIF.Business.Menu\clsMenuFavorite.vb` |

---

## BR-MENU-005: Menu-item validity and dirtiness cascade to security groups

| Field | Value |
| --- | --- |
| **Statement** | A menu item reports valid or clean only when its `SecurityGroups` child collection is also valid or clean. |
| **Trigger** | A caller validates or saves a `clsMenuItem`. |
| **Enforcement** | `clsMenuItem.IsValid`, `IsDirty`, and `SaveMe()` aggregate child state from `SecurityGroups`. |
| **Outcome** | Menu-item maintenance persists parent and child state as one logical unit. |
| **Tables** | [MENU_ITEM](database-tables.md#menuitem)<br>[MENU_SECURITY](database-tables.md#menusecurity) |
| **SQL** | [SQL-MENU-014](sql/SQL-MENU-014-insert-menu-item.md)<br>[SQL-MENU-016](sql/SQL-MENU-016-update-menu-item.md)<br>[SQL-MENU-024](sql/SQL-MENU-024-next-menu-security-sequence.md)<br>[SQL-MENU-026](sql/SQL-MENU-026-insert-menu-security-row.md)<br>[SQL-MENU-028](sql/SQL-MENU-028-update-menu-security-row.md)<br>[SQL-MENU-030](sql/SQL-MENU-030-retrieve-menu-security-for-item.md) |
| **Source** | `SAIF.Business.Menu\clsMenuItem.vb`<br>`SAIF.Business.Menu\clsMenuSecurityCollection.vb` |

---

## BR-MENU-006: Retrieve-all uses the UNSECURED wildcard sentinel

| Field | Value |
| --- | --- |
| **Statement** | The unfiltered authorized-menu query stamps public rows with `MenuUser = 'UNSECURED'`, and `Contains(userId, menuItem)` treats the sentinel as a wildcard match. |
| **Trigger** | A caller builds the all-users menu snapshot or checks it for a user. |
| **Enforcement** | `RetrieveAllSQL` emits the sentinel and `Contains(userId, menuItem)` matches it. |
| **Outcome** | All-users snapshots can answer public-item lookups without requerying per user. |
| **Tables** | [MENU_ITEM](database-tables.md#menuitem)<br>[MENU_SECURITY](database-tables.md#menusecurity) |
| **SQL** | [SQL-MENU-044](sql/SQL-MENU-044-retrieve-all-authorized-menu-items.md) |
| **Source** | `SAIF.Business.Menu\clsMenuAuthorizedItemCollection.vb`<br>`SAIF.Business.Menu\clsMenuAuthorizedItem.vb` |

---

## Inherited business rules

No `dependency-resolution.json` was produced for this run, so the following inherited rules are a best-effort inference from typical SAIF BOF framework (`clsBusinessBase` / `clsBusinessCollection`) patterns observed elsewhere in this codebase, not a Legends CLI-sourced dependency lookup.** Every public entity in this library (`clsApplicationCode`, `clsMenuAuthorizedItem`, `clsMenuCategoryCode`, `clsMenuFavorite`, `clsMenuItem`, `clsMenuPage`, `clsMenuSecurity`, `clsMenuSecurityUser`) inherits `clsBusinessBase`, and every public collection (`clsApplicationCodeCollection`, `clsMenuAuthorizedItemCollection`, `clsMenuCategoryCodeCollection`, `clsMenuFavoriteCollection`, `clsMenuItemCollection`, `clsMenuPageCollection`, `clsMenuSecurityCollection`, `clsMenuSecurityUserCollection`) inherits `clsBusinessCollection(Of T)`.

## BR-INH-001: Dirty/valid state gates persistence

| Field | Value |
| --- | --- |
| **Statement** | A `clsBusinessBase`-derived object only attempts an insert/update through `Save()` when its inherited `IsDirty` state is true and its inherited `IsValid` state is true.** |
| **Trigger** | A caller invokes `Save()` on any entity or collection in this library. |
| **Enforcement** | Framework-level `Save()` logic in `clsBusinessBase`/`clsBusinessCollection` checks dirty/valid flags before issuing the data-portal request.** |
| **Outcome** | Unchanged or invalid objects do not generate unnecessary or unsafe database writes. |
| **Origin** | `clsBusinessBase` / `clsBusinessCollection` (SAIF.BOF.Framework)** |
| **Source** | Inferred from override patterns in `SAIF.Business.Menu\clsMenuItem.vb`, `SAIF.Business.Menu\clsApplicationCode.vb`, and sibling entity/collection files, which each override `IsValid`/`IsDirty` to layer local rules atop the inherited base behavior.** |

---

## BR-INH-002: Collection membership tracks deleted rows separately

| Field | Value |
| --- | --- |
| **Statement** | Removing an item from a `clsBusinessCollection(Of T)`-derived collection moves it to an internal deleted-items list rather than discarding it, so a subsequent `Save()` can still issue the corresponding delete statement.** |
| **Trigger** | A caller calls `Remove` on any typed collection in this library. |
| **Enforcement** | Framework-level `Remove`/`ContainsDeleted` behavior in `clsBusinessCollection(Of T)`.** |
| **Outcome** | Deleted child rows (for example, removed `clsMenuSecurity` rows under `clsMenuItem.SecurityGroups`) are still deleted from the database on save rather than merely disappearing from the in-memory collection. |
| **Origin** | `clsBusinessCollection(Of T)` (SAIF.BOF.Framework)** |
| **Source** | Inferred from the `ContainsDeleted` members exposed on every collection type in this library (e.g. `clsMenuItemCollection.vb`, `clsMenuSecurityCollection.vb`).** |

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Security and authorization](security-authorization.md)
- [Database tables](database-tables.md)
- [SQL catalog](sql/index.md)
- [API reference](api/index.md)

> **Note:** Statements marked with `**` could not be confirmed; treat
> as unknown until verified.

## Sources
- `SAIF.Business.Menu\clsApplicationCode.vb`
- `SAIF.Business.Menu\clsMenuAuthorizedItem.vb`
- `SAIF.Business.Menu\clsMenuAuthorizedItemCollection.vb`
- `SAIF.Business.Menu\clsMenuFavorite.vb`
- `SAIF.Business.Menu\clsMenuItem.vb`
- `SAIF.Business.Menu\clsMenuPage.vb`
- `SAIF.Business.Menu\clsMenuSecurity.vb`
- `SAIF.Business.Menu\clsMenuSecurityUser.vb`
