Skip to main content

Delete semantics: hard-delete vs. soft-deactivate — match the legacy SP, don't invent an Eos-side rule

Audited 2026-08-03 across every entity with a delete path. There is deliberately no single Eos-wide mode — hard-delete and soft-deactivate coexist because the legacy SPs themselves aren't uniform, and retrofitting one shape onto all of them would fight the schema, not simplify it. What this section fixes is that the choice was previously made ad hoc, rediscovered per entity with no written criterion (see TASKS.md's now-closed "Document/enforce a hard-delete vs. soft-deactivate rule" item) — this names the criterion so the next entity's delete path is a decision, not a guess.

:::note No code changes came out of this pass Every existing entity's behavior already matches the rule below (with the two documented exceptions) — this section only makes it explicit. :::

The rule, in priority order

  1. If the entity's manage SP only implements one delete mode, use it. Most entities have no choice to make — e.g. MAS_ManWareHouseDt/ MAS_ManUserDt only ever offer soft-deactivate (Warehouse, User).

  2. If the SP offers both modes, default to whichever mode matches the entity's category below, unless a specific reason says otherwise — and if you deviate, write the reason down at the call site, the same way MenuRepository and RoleRepository already do (see the two deviations below).

  3. Two confirmed, deliberate deviations from the category default — both already correct, kept as-is (see below).

  4. New entities with no legacy SP at all (the MAS_CHOICES-style exception, see the Reference CRUD Pattern page) choose per the same category rule, implemented directly against EF Core — Country/Choice both hard-delete this way today.

Entity categories

  • Master-data/reference entities (City, Country, Branch, Company, Client, Department, Equipment, ForumType, Queue, TrCode, Address, UserGroup) → hard-delete. This is the existing majority pattern, and it's safe because every one of these SPs already guards the delete with its own referential-integrity check (e.g. Company blocks delete if still referenced elsewhere, TrCode blocks if still used in inventory) — Eos doesn't need to add a second guard, just trust the SP's.

  • Junction/assignment tables (GroupRoleAssignment, RoleMenuGrant, UserMenuOverride, and any future "X grants/is-assigned-to Y" table) → soft-deactivate (Active=0). This is already the consistent pattern across all three existing ones — "was this ever granted" has audit value, and re-granting later is a status flip, not a re-insert. Scenario (the automation engine's "IF trigger AND conditions THEN action" rule row, see the Automation Engine page) joined this bucket 2026-08-03 — same reasoning (Enabled=0), plus a structural forcing function: COM_SCENARIOS_LOG_DL_V2 has a non-cascading FK to MAS_SCENARIOS_V2, so a hard delete would fail outright for any scenario that has ever fired, not just be a worse choice.

  • Transactional entities (MasterOrder, WarehouseOrder, and their line tables) → hard-delete, but only by calling into the legacy SP's own cascade — never write a new cascading delete in Eos code. WarehouseOrder's outbound line unlink (WMS_CancelAllocateWOLine) is the one deliberate soft-delete exception in this category, because unlinking there also reverses an allocation and needs an audit row — a plain hard delete would lose that.

Confirmed deliberate deviations — do not "fix" these

:::note Known deviation — MenuRepository MAS_ManMenuDt supports both ActionFlag=3 (soft) and =4 (hard-cascade), but Eos only ever calls soft, by decision: hard-cascade would wipe MAS_APP_MENU/MAS_ROLE_MENU grouping/permission data that a later reactivation would need to re-enter by hand. :::

:::note Known deviation — RoleRepository MAS_ManRoleDt's own hard-delete branch (Active=-1) is broken against the real schema (MAS_ROLE.Active is an unsigned tinyint, can't hold -1), so DeleteAsync routes around it by calling Update with Active=0 instead. Not a stylistic choice — a workaround for a real SP bug. Flagged in TASKS.md as the strongest refactor candidate if the SP is ever fixed upstream (fixing the legacy SP itself is out of scope for Eos). :::

Reference files

WarehouseRepository.cs (SP-only-mode soft-deactivate), MenuRepository.cs (deliberate soft-over-available-hard), RoleRepository.cs (soft-as-workaround- for-broken-SP), GroupRoleAssignmentRepository.cs/RoleMenuGrantRepository.cs/ UserMenuOverrideRepository.cs (junction-table soft-deactivate), WarehouseOrderRepository.cs (CancelAllocationAsync, the transactional-category soft exception), ScenarioRepository.cs (junction-table soft-deactivate forced by a real FK constraint, not just category fit).