Skip to content

Phase B.1: Operational notes and assignments core - #3

Merged
henter36 merged 19 commits into
mainfrom
phase-b1-notes-core
Jul 19, 2026
Merged

Phase B.1: Operational notes and assignments core#3
henter36 merged 19 commits into
mainfrom
phase-b1-notes-core

Conversation

@henter36

@henter36 henter36 commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • Implements Phase B.1 vertical slice for Operational Notes and Assignments across Domain → React RTL.
  • Migration PhaseB1NotesCore; soft-deleted Role filters; Cryptography.Xml 10.0.10 pin retained.

Final assignment safety review

Pre-fix: 81cab5f577bfbfe0f02a3c5ca5e1cb7a2b048be6
Post-fix tip: 5feff19

Item Result
XOR assignment target in Application Service Fixed (EnsureExactlyOneAssignmentTarget)
Transition validated before mutating current assignment Fixed
Headquarters duplicate short-circuit Removed (Global short-circuit kept)
Concurrent first assign Unique index / concurrency → HTTP 409

Final verification

Gate Result
Unit 228 passed, 0 skipped
Integration 52 passed, 0 skipped
Frontend 78 passed, 0 skipped
Sonar Quality Gate Passed (Reliability/Maintainability/Security A; new issues 0)
Qlty / Gitleaks / CodeRabbit Passed
Open review threads 0

Explicit exclusions

B.2+, notifications, jobs, dashboard, reports, form builder, vehicles/AI/maps, etc.

Do not merge until explicit acceptance.

henter36 and others added 4 commits July 19, 2026 13:18
…ters.

Document the B.1 baseline from merged main, refresh README/A.1 report, add OperationalNote domain types and permissions, and filter UserRole/RolePermission through soft-deleted Role/User.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add OperationalNotes EF model (sequence, constraints, append-only history),
application services/workflow/SoD, API endpoints, permission seed, and tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Frontend: RTL React pages for notes list/create/edit/detail with workflow
actions, attachment upload, and permission-gated UI.

Backend gap closure:
- Server-side Classification filter on GET /api/v1/notes (was client-page-only).
- GET /api/v1/notes/{id}/attachments for metadata listing (scope-checked,
  404 for out-of-scope/missing, matching the existing attachment pattern).
- GET /api/v1/facility-units and /api/v1/departments lookups for cascading
  form dropdowns, replacing raw UUID inputs in NoteForm.
- Unit/integration tests proving soft-deleted Role rows never leak through
  UserRoles, RolePermissions, user listings, or PrivilegeGuard.
- Confirmed no EF 10622 warnings remain and the Cryptography.Xml 10.0.10
  pin (with its vulnerability-gate rationale) is intact.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @henter36, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Phase B.1 adds an operational notes feature spanning scoped domain entities, authorization, workflow APIs, SQL persistence, attachment handling, React pages, validation, tests, and project documentation.

Changes

Operational Notes

Layer / File(s) Summary
Domain, contracts, and persistence
src/backend/Baseera.Domain/Notes/*, src/backend/Baseera.Application/Notes/NoteDtos.cs, src/backend/Baseera.Infrastructure/Persistence/*
Adds note entities, DTOs, state constraints, row-version handling, SQL schema, sequence-based references, soft-delete filters, and append-only status history enforcement.
Application services and API
src/backend/Baseera.Application/Notes/*, src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs, src/backend/Baseera.Api/Authorization/*
Adds scoped queries, commands, assignments, workflow transitions, validators, permissions, organization lookups, attachment routing, and /api/v1/notes routes.
Frontend notes experience
src/frontend/src/notes/*, src/frontend/src/pages/notes/*, src/frontend/src/api/client.ts, src/frontend/src/App.tsx
Adds typed API calls, forms, filtering, sorting, workflow actions, attachments, permission gates, routes, and Arabic UI rendering.
Validation and documentation
src/backend/tests/*, src/frontend/src/**/*.test.*, docs/phase-b1-*, docs/permissions-matrix.md, README.md
Adds coverage for scope isolation, workflow transitions, SoD, concurrency, redaction, attachments, validators, frontend behavior, and Phase B.1 documentation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • henter36/Baseera#1: Provides the earlier centralized authorization and policy infrastructure extended for Notes.
  • henter36/Baseera#2: Provides related permission and route authorization wiring used by the Notes feature.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: the Phase B.1 operational notes and assignments core slice.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-b1-notes-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request delivers the core Operational Notes and Assignments functionality (Phase B.1), introducing the OperationalNote domain model, workflow state machine, organizational scope controls, and a complete React frontend. The code changes are well-structured and accompanied by extensive test coverage. However, the code review correctly identifies a pervasive issue across the backend services: the use of synchronous blocking database calls (such as FirstOrDefault, ToList, and Any) inside asynchronous methods, which can cause thread pool starvation under load. To ensure scalability, these should be converted to their asynchronous counterparts (e.g., FirstOrDefaultAsync, ToListAsync, AnyAsync) along with eager loading of navigation properties to prevent N+1 query issues.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/backend/Baseera.Application/Notes/NoteAssignmentService.cs Outdated
Comment thread src/backend/Baseera.Application/Notes/NoteQueryService.cs Outdated
Comment thread src/backend/Baseera.Application/Notes/NoteQueryService.cs Outdated
Comment thread src/backend/Baseera.Application/Notes/NoteQueryService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteQueryService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteWorkflowService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteQueryService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteCommandService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteAssignmentService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteAccessHelper.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (15)
src/backend/Baseera.Application/Notes/NoteQueryService.cs (1)

244-334: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce ApplyFilters cognitive complexity per static analysis.

SonarCloud flags this method at 19/15 allowed complexity due to the long sequential chain of independent if filters. Consider grouping related filters (search/status/severity/category, region/facility/unit/department, date ranges, overdue/assignment) into small private helper methods composed together.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteQueryService.cs` around lines 244 -
334, Reduce cognitive complexity in NoteQueryService.ApplyFilters by extracting
the independent filter groups into small private helper methods for text/status
attributes, organizational scope, date ranges, and overdue/assignment criteria.
Have ApplyFilters compose these helpers in the same order while preserving every
existing predicate and behavior, including the now value and assignment checks.

Source: Linters/SAST tools

src/frontend/src/pages/notes/NoteEditPage.tsx (1)

130-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace magic numbers with enum constants.

Using raw integers (5 and 7) for note statuses obscures the logic and makes it harder to maintain. Consider using the corresponding exported enum values (e.g., NoteStatus.Closed and NoteStatus.Cancelled) from your domain enums.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/pages/notes/NoteEditPage.tsx` around lines 130 - 132, Update
the status check in NoteEditPage to replace the magic numbers 5 and 7 with the
corresponding exported NoteStatus enum members, such as NoteStatus.Closed and
NoteStatus.Cancelled. Preserve the existing alert and edit-blocking behavior.
src/frontend/src/api/client.ts (1)

353-376: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce buildNoteQuery cognitive complexity to unblock the SonarCloud check.

The SonarCloud gate fails here (complexity 18 > 15). A small setIf helper collapses the repeated branches while preserving the !== undefined handling that keeps 0-valued enums (e.g. status/classification).

♻️ Proposed refactor
 function buildNoteQuery(filters: NoteListFilters): string {
   const params = new URLSearchParams()
   params.set('page', String(filters.page ?? 1))
   params.set('pageSize', String(filters.pageSize ?? 20))
-  if (filters.search) params.set('search', filters.search)
-  if (filters.status !== undefined) params.set('status', String(filters.status))
-  if (filters.severity !== undefined) params.set('severity', String(filters.severity))
-  if (filters.category !== undefined) params.set('category', String(filters.category))
-  if (filters.sourceType !== undefined) params.set('sourceType', String(filters.sourceType))
-  if (filters.classification !== undefined) params.set('classification', String(filters.classification))
-  if (filters.regionId) params.set('regionId', filters.regionId)
-  if (filters.facilityId) params.set('facilityId', filters.facilityId)
-  if (filters.facilityUnitId) params.set('facilityUnitId', filters.facilityUnitId)
-  if (filters.ownerDepartmentId) params.set('ownerDepartmentId', filters.ownerDepartmentId)
-  if (filters.assignedToUserId) params.set('assignedToUserId', filters.assignedToUserId)
-  if (filters.overdueOnly) params.set('overdueOnly', 'true')
-  if (filters.dueFrom) params.set('dueFrom', filters.dueFrom)
-  if (filters.dueTo) params.set('dueTo', filters.dueTo)
-  if (filters.createdFrom) params.set('createdFrom', filters.createdFrom)
-  if (filters.createdTo) params.set('createdTo', filters.createdTo)
-  if (filters.sortBy) params.set('sortBy', filters.sortBy)
-  if (filters.sortDesc) params.set('sortDesc', 'true')
+  const setIf = (key: string, value: string | number | undefined) => {
+    if (value === undefined || value === '') return
+    params.set(key, String(value))
+  }
+  setIf('search', filters.search)
+  setIf('status', filters.status)
+  setIf('severity', filters.severity)
+  setIf('category', filters.category)
+  setIf('sourceType', filters.sourceType)
+  setIf('classification', filters.classification)
+  setIf('regionId', filters.regionId)
+  setIf('facilityId', filters.facilityId)
+  setIf('facilityUnitId', filters.facilityUnitId)
+  setIf('ownerDepartmentId', filters.ownerDepartmentId)
+  setIf('assignedToUserId', filters.assignedToUserId)
+  if (filters.overdueOnly) params.set('overdueOnly', 'true')
+  setIf('dueFrom', filters.dueFrom)
+  setIf('dueTo', filters.dueTo)
+  setIf('createdFrom', filters.createdFrom)
+  setIf('createdTo', filters.createdTo)
+  setIf('sortBy', filters.sortBy)
+  if (filters.sortDesc) params.set('sortDesc', 'true')
   return params.toString()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/api/client.ts` around lines 353 - 376, Reduce cognitive
complexity in buildNoteQuery by introducing a small setIf helper for conditional
query-parameter assignment, then use it for the repeated optional filters.
Preserve explicit !== undefined checks for enum-like values such as status,
severity, category, sourceType, and classification so 0 remains valid, while
retaining truthy checks for the existing string/boolean filters and unchanged
defaults.

Source: Linters/SAST tools

src/frontend/src/pages/notes/NoteDetailPage.tsx (3)

129-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce cognitive complexity in run() to satisfy the SonarCloud gate.

SonarCloud reports this callback at complexity 20 (limit 15) as a [failure], which may block the quality gate. Extracting the per-action.kind dispatch (the switch) into a small helper and the catch error-status mapping (409/403/404/else) into a separate mapError function will bring it under the threshold without behavior change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/pages/notes/NoteDetailPage.tsx` around lines 129 - 207, The
run callback in the action component exceeds the cognitive-complexity limit.
Extract the action.kind switch into a focused dispatch helper and move the
ApiError status mapping (409, 403, 404, and fallback) into a separate mapError
function, then have run() call them while preserving all existing validation,
messages, conflict handling, and pending-state behavior.

Source: Linters/SAST tools


388-393: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer named enum constants over magic status numbers.

AttachmentScanStatus.Clean (=== 1) and NoteStatus.Closed/NoteStatus.Cancelled (5/7) are already defined in ../../notes/noteEnums; using them keeps the UI in lockstep with the backend enum values.

♻️ Suggested change
-  const canEdit = canUpdate && note.status !== 5 && note.status !== 7
+  const canEdit = canUpdate && note.status !== NoteStatus.Closed && note.status !== NoteStatus.Cancelled

Apply the same for a.scanStatus === 1a.scanStatus === AttachmentScanStatus.Clean at Lines 388 and 393, importing AttachmentScanStatus and NoteStatus from noteEnums.

Also applies to: 474-474

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/pages/notes/NoteDetailPage.tsx` around lines 388 - 393,
Replace the magic status comparisons in the note detail UI with the named
AttachmentScanStatus.Clean, NoteStatus.Closed, and NoteStatus.Cancelled
constants. Import these enums from noteEnums and update the scan-status checks
at the badge and conditional-rendering sites, plus the additional status check
around line 474, while preserving the existing behavior.

408-624: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce cognitive complexity in NoteDetailPage to satisfy the SonarCloud gate.

SonarCloud reports this component at complexity 17 (limit 15) as a [failure]. Extracting the early-return branches (loading/error/not-found) into a small guard component, or the error-message nested ternary (L447-451) into a helper, will bring it under the threshold.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/pages/notes/NoteDetailPage.tsx` around lines 408 - 624,
Reduce cognitive complexity in NoteDetailPage by extracting the loading, error,
and missing-note early-return branches into a small guard component or helper,
or by moving the nested error-message logic into a named helper. Preserve the
existing permission checks, Arabic messages, retry behavior, and normal
rendering flow while bringing the component below the SonarCloud threshold.

Source: Linters/SAST tools

src/backend/tests/Baseera.IntegrationTests/NotesCoreIntegrationTests.cs (1)

244-303: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate note-creation/transition test helpers across the two new integration test files; already diverging.

Both files independently implement near-identical CreateNoteAsync/transition-posting helpers for the same Notes API, and they've already drifted (only one supports a severity parameter). Extract a shared helper to avoid further divergence.

  • src/backend/tests/Baseera.IntegrationTests/NotesCoreIntegrationTests.cs#L244-L303: move CreateNoteAsync, PostTransitionAsync, PostWorkflowAsync, AssignAsync into a shared internal test-helper type reused by both classes.
  • src/backend/tests/Baseera.IntegrationTests/NotesAdditionalIntegrationTests.cs#L387-L422: replace the local CreateNoteAsync/PostAsync duplicates with calls to the shared helper (adding the missing severity parameter support there).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/tests/Baseera.IntegrationTests/NotesCoreIntegrationTests.cs`
around lines 244 - 303, Extract CreateNoteAsync, PostTransitionAsync,
PostWorkflowAsync, and AssignAsync from NotesCoreIntegrationTests.cs lines
244-303 into a shared internal test-helper type, preserving severity support and
existing request/assertion behavior. Update NotesAdditionalIntegrationTests.cs
lines 387-422 to remove its local CreateNoteAsync/PostAsync duplicates and call
the shared helper, including the missing severity parameter support; update all
affected call sites to use the shared transition helper names as needed.
src/backend/Baseera.Application/Notes/NoteValidators.cs (1)

1-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated BeMeaningful helper.

The identical BeMeaningful private method is copy-pasted into all seven validator classes in this file. Consider a shared static class or a FluentValidation extension method (e.g. RuleFor(x => x.Reason).MustBeMeaningful()) to remove the duplication.

♻️ Example shared helper
internal static class NoteValidationRules
{
    public static bool BeMeaningful(string? value) => !string.IsNullOrWhiteSpace(value);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteValidators.cs` around lines 1 -
102, Extract the duplicated BeMeaningful methods from all seven validators into
one shared internal helper, such as NoteValidationRules, and update each Must
reference to use it. Remove the per-class private helpers while preserving the
existing validation behavior and messages.
src/backend/Baseera.Application/Notes/NoteCommandService.cs (1)

235-249: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated helpers and magic string across NoteCommandService and NoteWorkflowService. Both services independently redeclare identical RequireUserId/AppendHistory private methods and repeat the Module = "Notes" literal across their AuditEntry calls (flagged by SonarCloud in both files).

  • src/backend/Baseera.Application/Notes/NoteCommandService.cs#L235-L249: move RequireUserId/AppendHistory into a shared helper (e.g. extend NoteAccessHelper) and replace the "Notes" literal with a shared constant.
  • src/backend/Baseera.Application/Notes/NoteWorkflowService.cs#L275-L290: remove the duplicate RequireUserId/AppendHistory implementations in favor of the same shared helper/constant.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteCommandService.cs` around lines 235
- 249, The note services duplicate RequireUserId and AppendHistory and repeat
the "Notes" audit module literal. Move the shared helper behavior into
NoteAccessHelper, define a shared constant for the Notes module, and update
NoteCommandService.cs lines 235-249 and NoteWorkflowService.cs lines 275-290 to
use them; remove both private helper implementations and replace every affected
AuditEntry module literal with the constant.

Source: Linters/SAST tools

src/backend/Baseera.Application/Notes/NoteWorkflowService.cs (1)

170-178: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

TransitionAsync has 8 parameters.

Consider grouping the transition metadata (permission, toStatus, auditAction, apply) into a small options object/record to reduce the parameter count and improve readability at call sites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteWorkflowService.cs` around lines
170 - 178, Reduce the parameter count of TransitionAsync by introducing a small
options object or record containing permission, toStatus, auditAction, and
apply. Update TransitionAsync and all call sites to pass this grouped transition
metadata while keeping id, rowVersion, reason, and cancellationToken as separate
parameters.

Source: Linters/SAST tools

src/backend/Baseera.Infrastructure/Persistence/Migrations/20260719103156_PhaseB1NotesCore.cs (1)

200-297: 🚀 Performance & Scalability | 🔵 Trivial

Schema/constraints look correct; consider indexing additional list-filter columns.

FKs, check constraints, and the filtered-unique IX_NoteAssignments_OperationalNoteId index correctly enforce the domain invariants (scope shape, assignment XOR, single-current-assignment). One operational note: /api/v1/notes supports filtering by Category, Classification, and SourceType (see ApiEndpoints.cs NoteListQuery), but no indexes are created for these columns, only Severity/Status/etc. Once the table grows, these filters will do full scans.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/backend/Baseera.Infrastructure/Persistence/Migrations/20260719103156_PhaseB1NotesCore.cs`
around lines 200 - 297, Update the migration’s OperationalNotes index
definitions to add non-unique indexes for the Category, Classification, and
SourceType columns used by NoteListQuery in ApiEndpoints.cs. Keep the existing
Severity, Status, and other indexes unchanged.
src/backend/Baseera.Application/Notes/NoteAssignmentService.cs (3)

121-137: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unused cancellationToken parameter; method is fully synchronous.

Sonar flags the unused cancellationToken parameter. This is symptomatic of the method being entirely synchronous under an async/Task-returning signature (db.UsersIncludingDeleted.FirstOrDefault, and the calls it makes are also sync) — see the consolidated comment on synchronous EF Core calls blocking async request threads across this cohort.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs` around lines
121 - 137, Update ValidateAssigneeUserAsync to use a synchronous return type and
remove the unused cancellationToken parameter, since its database lookup and
validation calls are synchronous. Adjust its caller to invoke it without
awaiting and preserve the existing validation and exception behavior.

Source: Linters/SAST tools


39-50: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Concurrent first-time assignment race isn't translated into a friendly error.

Two simultaneous AssignAsync calls on a not-yet-assigned note can both read current == null; the DB's filtered unique index will reject the second INSERT, but that surfaces as a raw DbUpdateException/500 rather than the same kind of friendly conflict message used for row-version mismatches (NoteAccessHelper.EnsureRowVersion). Rare in practice, but worth catching and translating for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs` around lines
39 - 50, Update AssignAsync to catch the DbUpdateException raised when the
filtered unique index rejects a concurrent first-time assignment, and translate
that case into the same friendly conflict error used by
NoteAccessHelper.EnsureRowVersion. Preserve existing exception behavior for
unrelated database failures and keep normal reassignment handling unchanged.

200-221: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

IntersectsNote: high cognitive complexity and a per-scope DB round-trip.

Sonar flags this method's cognitive complexity (25 vs. 15 allowed). Separately, the Region/Facility/FacilityUnit branches call db.Facilities.Any(...) once per entry in the in-memory scopes list — for a user with several scope rows this issues multiple sequential DB round-trips instead of one batched query (e.g., pre-loading the relevant facilities' region IDs once, as NoteScopeService.FilterQueryable does with ToList()).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs` around lines
200 - 221, Refactor IntersectsNote into small scope-specific helpers to reduce
its cognitive complexity while preserving all existing scope-matching behavior.
Batch-load the required facility-to-region mappings once before evaluating
Region, Facility, and FacilityUnit scopes, then pass the in-memory results to
the helpers instead of calling db.Facilities.Any inside scopes.Any. Follow the
existing FilterQueryable approach of materializing the relevant facility data
with ToList().

Source: Linters/SAST tools

src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs (1)

183-232: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bind the 22-parameter /notes list handler via [AsParameters].

Static analysis flags this lambda for having 22 parameters (max 7 recommended). Since almost every parameter maps 1:1 onto NoteListQuery, binding a parameter record with [AsParameters] (minimal-API feature) would collapse this to a single parameter and improve readability/testability without changing behavior.

♻️ Sketch of the refactor
public sealed class NoteListRequest
{
    public int? Page { get; init; }
    public int? PageSize { get; init; }
    // ...remaining fields matching NoteListQuery
}

notes.MapGet("/", async (
    [AsParameters] NoteListRequest request,
    INoteQueryService queries,
    CancellationToken ct) => Results.Ok(await queries.ListAsync(request.ToQuery(), ct)))
    .RequireAuthorization(AuthPolicies.NotesView);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs` around lines 183 - 232,
Refactor the `/notes` handler in MapNotesEndpoints to accept a single
[AsParameters]-bound NoteListRequest containing the existing query fields, plus
INoteQueryService and CancellationToken. Add a conversion method such as ToQuery
that maps the request to NoteListQuery while preserving all current defaults,
filters, and sorting behavior, then pass that result to queries.ListAsync.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/backend/Baseera.Application/Attachments/AttachmentAppService.cs`:
- Around line 58-63: Update AttachmentAppService.ListForEntityAsync to apply the
same sensitivity and permission gating as DownloadAsync before mapping results.
Ensure callers with only Notes.View cannot receive confidential attachment
metadata; either filter unauthorized sensitive attachments or redact their
sensitive fields, while preserving visibility of permitted entries.

In `@src/backend/Baseera.Application/Notes/NoteAccessHelper.cs`:
- Around line 42-56: Replace synchronous EF Core queries with cancellable
asynchronous equivalents across the Notes authorization path. In
src/backend/Baseera.Application/Notes/NoteAccessHelper.cs lines 42-56, make
LoadInScopeOrNotFound async, await FirstOrDefaultAsync, and update callers. In
src/backend/Baseera.Application/Notes/NoteScopeService.cs lines 60-77 and
172-199, use ToListAsync and async validation methods, threading
CancellationToken through EnsureOrgEntitiesActiveAsync and related calls. In
src/backend/Baseera.Application/Notes/NoteAssignmentService.cs lines 121-198,
convert ValidateAssigneeUserAsync, ValidateAssigneeDepartment,
EnsureAssigneeCanWork, and EnsureAssigneeScopeIntersects to FirstOrDefaultAsync,
AnyAsync, or ToListAsync, and pass through the existing cancellationToken.

In `@src/backend/Baseera.Application/Notes/NoteCommandService.cs`:
- Around line 110-148: Update the audit payloads in
NoteCommandService.UpdateAsync to include SourceType, SourceReference, and
OwnerDepartmentId in both the old snapshot and NewValues, matching the fields
mutated before saving. Preserve the existing audit entry structure and values
for all other tracked note fields.
- Around line 251-259: Update NormalizeRegionId to use an awaited
FirstOrDefaultAsync facility lookup instead of synchronous First, and explicitly
handle a missing facility with the service’s established domain error behavior
before returning its RegionId. Ensure the containing async flow awaits the
lookup and preserves the existing request.RegionId fallback.

In `@src/backend/Baseera.Application/Notes/NoteQueryService.cs`:
- Around line 196-242: Eliminate the per-assignment database lookups in
GetAssignmentsAsync and MapAssignment by batch-loading all referenced users and
departments into dictionaries before mapping, following the existing ListAsync
pattern. Update MapAssignment to consume those dictionaries for display names
while preserving the current DTO fields and ordering.
- Around line 28-100: Replace synchronous EF Core execution throughout
NoteQueryService methods ListAsync, GetDetailAsync, GetHistoryAsync, and
GetAssignmentsAsync with the corresponding async operations, including
CountAsync, ToListAsync, ToDictionaryAsync, and FirstOrDefaultAsync, passing the
existing cancellationToken. Remove Task.FromResult wrappers and return the
materialized results directly while preserving the current filtering, paging,
mapping, and response behavior.

In `@src/backend/Baseera.Application/Notes/NoteScopeService.cs`:
- Around line 115-138: Update EnsureOrgEntitiesActiveAsync so a present
facilityUnitId is explicitly validated against a non-null facilityId before
accessing the facility ID; return the existing clear validation error for the
invalid shape, then call EnsureUnitBelongsToFacility only with the validated
value and remove the null-forgiving access.

In `@src/backend/Baseera.Application/Notes/NoteWorkflowService.cs`:
- Around line 228-239: The EnforceCriticalSoD method only checks the mutable
LastProcessedByUserId and can miss earlier processors. For critical notes,
inspect the complete append-only NoteStatusHistory (or an equivalent
all-processors collection) and reject closure when closerId appears among any
prior processors, while preserving the existing non-critical behavior and
exception.

In `@src/backend/Baseera.Application/Organization/OrganizationService.cs`:
- Around line 207-231: Update the UnauthorizedAccessException message in
ListFacilityUnitsAsync to use the Arabic term for “facility” instead of
“prison,” while preserving the existing authorization behavior and message
structure.

In `@src/frontend/src/pages/notes/NoteEditPage.tsx`:
- Around line 153-157: Update the reload button handler in NoteEditPage to clear
both conflict and serverError state before or while invoking
noteQuery.refetch(). Preserve the existing refetch behavior and ensure reloading
allows the submit action again.

---

Nitpick comments:
In `@src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs`:
- Around line 183-232: Refactor the `/notes` handler in MapNotesEndpoints to
accept a single [AsParameters]-bound NoteListRequest containing the existing
query fields, plus INoteQueryService and CancellationToken. Add a conversion
method such as ToQuery that maps the request to NoteListQuery while preserving
all current defaults, filters, and sorting behavior, then pass that result to
queries.ListAsync.

In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs`:
- Around line 121-137: Update ValidateAssigneeUserAsync to use a synchronous
return type and remove the unused cancellationToken parameter, since its
database lookup and validation calls are synchronous. Adjust its caller to
invoke it without awaiting and preserve the existing validation and exception
behavior.
- Around line 39-50: Update AssignAsync to catch the DbUpdateException raised
when the filtered unique index rejects a concurrent first-time assignment, and
translate that case into the same friendly conflict error used by
NoteAccessHelper.EnsureRowVersion. Preserve existing exception behavior for
unrelated database failures and keep normal reassignment handling unchanged.
- Around line 200-221: Refactor IntersectsNote into small scope-specific helpers
to reduce its cognitive complexity while preserving all existing scope-matching
behavior. Batch-load the required facility-to-region mappings once before
evaluating Region, Facility, and FacilityUnit scopes, then pass the in-memory
results to the helpers instead of calling db.Facilities.Any inside scopes.Any.
Follow the existing FilterQueryable approach of materializing the relevant
facility data with ToList().

In `@src/backend/Baseera.Application/Notes/NoteCommandService.cs`:
- Around line 235-249: The note services duplicate RequireUserId and
AppendHistory and repeat the "Notes" audit module literal. Move the shared
helper behavior into NoteAccessHelper, define a shared constant for the Notes
module, and update NoteCommandService.cs lines 235-249 and
NoteWorkflowService.cs lines 275-290 to use them; remove both private helper
implementations and replace every affected AuditEntry module literal with the
constant.

In `@src/backend/Baseera.Application/Notes/NoteQueryService.cs`:
- Around line 244-334: Reduce cognitive complexity in
NoteQueryService.ApplyFilters by extracting the independent filter groups into
small private helper methods for text/status attributes, organizational scope,
date ranges, and overdue/assignment criteria. Have ApplyFilters compose these
helpers in the same order while preserving every existing predicate and
behavior, including the now value and assignment checks.

In `@src/backend/Baseera.Application/Notes/NoteValidators.cs`:
- Around line 1-102: Extract the duplicated BeMeaningful methods from all seven
validators into one shared internal helper, such as NoteValidationRules, and
update each Must reference to use it. Remove the per-class private helpers while
preserving the existing validation behavior and messages.

In `@src/backend/Baseera.Application/Notes/NoteWorkflowService.cs`:
- Around line 170-178: Reduce the parameter count of TransitionAsync by
introducing a small options object or record containing permission, toStatus,
auditAction, and apply. Update TransitionAsync and all call sites to pass this
grouped transition metadata while keeping id, rowVersion, reason, and
cancellationToken as separate parameters.

In
`@src/backend/Baseera.Infrastructure/Persistence/Migrations/20260719103156_PhaseB1NotesCore.cs`:
- Around line 200-297: Update the migration’s OperationalNotes index definitions
to add non-unique indexes for the Category, Classification, and SourceType
columns used by NoteListQuery in ApiEndpoints.cs. Keep the existing Severity,
Status, and other indexes unchanged.

In `@src/backend/tests/Baseera.IntegrationTests/NotesCoreIntegrationTests.cs`:
- Around line 244-303: Extract CreateNoteAsync, PostTransitionAsync,
PostWorkflowAsync, and AssignAsync from NotesCoreIntegrationTests.cs lines
244-303 into a shared internal test-helper type, preserving severity support and
existing request/assertion behavior. Update NotesAdditionalIntegrationTests.cs
lines 387-422 to remove its local CreateNoteAsync/PostAsync duplicates and call
the shared helper, including the missing severity parameter support; update all
affected call sites to use the shared transition helper names as needed.

In `@src/frontend/src/api/client.ts`:
- Around line 353-376: Reduce cognitive complexity in buildNoteQuery by
introducing a small setIf helper for conditional query-parameter assignment,
then use it for the repeated optional filters. Preserve explicit !== undefined
checks for enum-like values such as status, severity, category, sourceType, and
classification so 0 remains valid, while retaining truthy checks for the
existing string/boolean filters and unchanged defaults.

In `@src/frontend/src/pages/notes/NoteDetailPage.tsx`:
- Around line 129-207: The run callback in the action component exceeds the
cognitive-complexity limit. Extract the action.kind switch into a focused
dispatch helper and move the ApiError status mapping (409, 403, 404, and
fallback) into a separate mapError function, then have run() call them while
preserving all existing validation, messages, conflict handling, and
pending-state behavior.
- Around line 388-393: Replace the magic status comparisons in the note detail
UI with the named AttachmentScanStatus.Clean, NoteStatus.Closed, and
NoteStatus.Cancelled constants. Import these enums from noteEnums and update the
scan-status checks at the badge and conditional-rendering sites, plus the
additional status check around line 474, while preserving the existing behavior.
- Around line 408-624: Reduce cognitive complexity in NoteDetailPage by
extracting the loading, error, and missing-note early-return branches into a
small guard component or helper, or by moving the nested error-message logic
into a named helper. Preserve the existing permission checks, Arabic messages,
retry behavior, and normal rendering flow while bringing the component below the
SonarCloud threshold.

In `@src/frontend/src/pages/notes/NoteEditPage.tsx`:
- Around line 130-132: Update the status check in NoteEditPage to replace the
magic numbers 5 and 7 with the corresponding exported NoteStatus enum members,
such as NoteStatus.Closed and NoteStatus.Cancelled. Preserve the existing alert
and edit-blocking behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2987d988-add0-4c52-bce3-73b6cd3c7b63

📥 Commits

Reviewing files that changed from the base of the PR and between 5dc0b5b and 6e0b27c.

📒 Files selected for processing (72)
  • README.md
  • docs/permissions-matrix.md
  • docs/phase-a1-completion-report.md
  • docs/phase-b1-api-contract.md
  • docs/phase-b1-baseline.md
  • docs/phase-b1-completion-report.md
  • docs/phase-b1-domain-model.md
  • docs/phase-b1-permissions-and-scope.md
  • docs/phase-b1-scope.md
  • docs/phase-b1-state-machine.md
  • docs/phase-b1-test-matrix.md
  • src/backend/Baseera.Api/Authorization/AuthorizationExtensions.cs
  • src/backend/Baseera.Api/Baseera.Api.csproj
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/backend/Baseera.Application/Abstractions/Abstractions.cs
  • src/backend/Baseera.Application/Attachments/AttachmentAppService.cs
  • src/backend/Baseera.Application/Attachments/AttachmentRules.cs
  • src/backend/Baseera.Application/DependencyInjection/ApplicationServiceCollectionExtensions.cs
  • src/backend/Baseera.Application/Notes/NoteAccessHelper.cs
  • src/backend/Baseera.Application/Notes/NoteAssignmentService.cs
  • src/backend/Baseera.Application/Notes/NoteCommandService.cs
  • src/backend/Baseera.Application/Notes/NoteDtos.cs
  • src/backend/Baseera.Application/Notes/NoteQueryService.cs
  • src/backend/Baseera.Application/Notes/NoteScopeService.cs
  • src/backend/Baseera.Application/Notes/NoteStateMachine.cs
  • src/backend/Baseera.Application/Notes/NoteValidators.cs
  • src/backend/Baseera.Application/Notes/NoteWorkflowService.cs
  • src/backend/Baseera.Application/Organization/OrganizationService.cs
  • src/backend/Baseera.Domain/Identity/IdentityEntities.cs
  • src/backend/Baseera.Domain/Notes/NoteEntities.cs
  • src/backend/Baseera.Infrastructure/Attachments/AttachmentService.cs
  • src/backend/Baseera.Infrastructure/Persistence/BaseeraDbContext.cs
  • src/backend/Baseera.Infrastructure/Persistence/Configurations/NoteConfigurations.cs
  • src/backend/Baseera.Infrastructure/Persistence/DatabaseInitializer.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/20260719103156_PhaseB1NotesCore.Designer.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/20260719103156_PhaseB1NotesCore.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/BaseeraDbContextModelSnapshot.cs
  • src/backend/tests/Baseera.IntegrationTests/NotesAdditionalIntegrationTests.cs
  • src/backend/tests/Baseera.IntegrationTests/NotesCoreIntegrationTests.cs
  • src/backend/tests/Baseera.IntegrationTests/OrganizationLookupIntegrationTests.cs
  • src/backend/tests/Baseera.UnitTests/AttachmentRulesTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteAssignmentServiceTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteCommandServiceTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteQueryServiceTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteScopeServiceTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteStateMachineTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteTestFixtures.cs
  • src/backend/tests/Baseera.UnitTests/NoteValidatorsTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteWorkflowServiceTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteWorkflowTests.cs
  • src/backend/tests/Baseera.UnitTests/PrivilegeProvisioningSoftDeleteTests.cs
  • src/frontend/src/App.tsx
  • src/frontend/src/api/client.ts
  • src/frontend/src/index.css
  • src/frontend/src/notes/NoteForm.tsx
  • src/frontend/src/notes/noteEnums.ts
  • src/frontend/src/notes/noteSchema.test.ts
  • src/frontend/src/notes/noteSchema.ts
  • src/frontend/src/notes/noteScopeOptions.ts
  • src/frontend/src/notes/noteWorkflow.test.ts
  • src/frontend/src/notes/noteWorkflow.ts
  • src/frontend/src/pages/notes/NoteCreatePage.permission.test.tsx
  • src/frontend/src/pages/notes/NoteCreatePage.test.tsx
  • src/frontend/src/pages/notes/NoteCreatePage.tsx
  • src/frontend/src/pages/notes/NoteDetailPage.test.tsx
  • src/frontend/src/pages/notes/NoteDetailPage.tsx
  • src/frontend/src/pages/notes/NoteEditPage.permission.test.tsx
  • src/frontend/src/pages/notes/NoteEditPage.test.tsx
  • src/frontend/src/pages/notes/NoteEditPage.tsx
  • src/frontend/src/pages/notes/NotesListPage.permission.test.tsx
  • src/frontend/src/pages/notes/NotesListPage.test.tsx
  • src/frontend/src/pages/notes/NotesListPage.tsx

Comment thread src/backend/Baseera.Application/Attachments/AttachmentAppService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteAccessHelper.cs Outdated
Comment thread src/backend/Baseera.Application/Notes/NoteCommandService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteCommandService.cs Outdated
Comment thread src/backend/Baseera.Application/Notes/NoteQueryService.cs
Comment thread src/backend/Baseera.Application/Notes/NoteQueryService.cs Outdated
Comment thread src/backend/Baseera.Application/Notes/NoteScopeService.cs Outdated
Comment thread src/backend/Baseera.Application/Notes/NoteWorkflowService.cs Outdated
Comment thread src/backend/Baseera.Application/Organization/OrganizationService.cs Outdated
Comment thread src/frontend/src/pages/notes/NoteEditPage.tsx
henter36 and others added 4 commits July 19, 2026 16:03
…lls to async

Add Microsoft.EntityFrameworkCore to Baseera.Application so the async-first
Notes services (NoteAccessHelper, NoteQueryService, NoteCommandService,
NoteAssignmentService, NoteWorkflowService, NoteScopeService) and the new
OrganizationService list endpoints use *Async EF operators instead of
blocking FirstOrDefault/ToList/Count/Any calls inside async methods. Also
eager-load assignment navigations (AssignedToUser/AssignedToDepartment/
AssignedByUser) to remove N+1 lookups in NoteQueryService.MapAssignment.
Behavior (soft-delete, scope filtering, SoD checks) is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reliability bugs (blocking the Quality Gate):
- NoteScopeService.EnsureOrgEntitiesActiveAsync: replace the null-forgiving
  facilityId!.Value with an explicit guard, fixing the "facilityId is null on
  at least one execution path" bug without changing scope-validation behavior.
- OrganizationService: convert the remaining sync EF Core calls (Count,
  ToList, FirstOrDefault, Any) in ListRegionsAsync, GetRegionAsync,
  UpdateRegionAsync, ListFacilitiesAsync, GetFacilityAsync and
  CreateFacilityAsync to their async equivalents.

Cognitive complexity (>15) refactors, no behavior change:
- NoteScopeService.FilterQueryable: extract scope-id computation into
  BuildAccessibleScopeIds/ExpandRegionsFromAccessibleFacilities/
  ExpandFacilitiesFromAccessibleRegions helpers.
- NoteQueryService.ApplyFilters: split into ApplyTextAndEnumFilters/
  ApplyScopeFilters/ApplyDateFilters/ApplyAssignmentFilter.
- ApiEndpoints.MapBaseeraApi: extract the /attachments upload handler into
  UploadAttachmentAsync.
- client.ts buildNoteQuery: split into appendPagingParams/
  appendEnumFilterParams/appendScopeFilterParams/appendDateRangeParams.
- NoteDetailPage.tsx: extract ActionPanel's run() into validateActionInputs/
  performNoteAction/describeActionError, and split the page body into
  NoteSummaryGrid/NoteDescriptionSection/CurrentAssignmentSection/
  AssignmentsHistorySection/StatusTimelineSection/NoteActionsSection.

Other Sonar warnings cleaned up:
- Mark React component props as Readonly<...> (NoteForm, NoteDetailPage
  subcomponents).
- Use HashSet.OfType instead of Where+Cast in NoteQueryService.
- Introduce NoteAccessHelper.ModuleName / DatabaseInitializer.NotesModule
  constants instead of repeating the "Notes" literal.
- Make NoteWorkflowService.EnforceCriticalSoD static.
- Wrap NoteWorkflowService.TransitionAsync's 8 parameters in a
  TransitionOptions record.
- Replace nested ternaries with describeNoteLoadError/sortIndicator helpers.
- Prefer optional chaining, output/role=status, and childNode.remove() over
  removeChild in the Notes pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Redact confidential attachment list metadata without DownloadSensitive, expand NoteUpdated audit fields, clear edit-page conflict state on reload, and use async scope expansion on note lists.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/backend/Baseera.Application/Notes/NoteAssignmentService.cs (2)

40-52: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Validate the transition before mutating the existing assignment.

current is mutated (IsCurrent = false, EndedAtUtc, EndReason, db.Update) before EnsureAssignTransition runs. Today this is safe only because SaveChangesAsync is deferred to Line 101, but the side effect happening before validation is a latent trap for future refactors (e.g. an early save, or reuse of this context in a broader unit-of-work). Reorder so failed transitions never touch tracked state.

♻️ Proposed reorder
         var current = await db.NoteAssignments.FirstOrDefaultAsync(a => a.OperationalNoteId == id && a.IsCurrent, cancellationToken);
         var isReassign = current is not null;
         var fromStatus = note.Status;

+        EnsureAssignTransition(note.Status, isReassign);
+
         if (current is not null)
         {
             current.IsCurrent = false;
             current.EndedAtUtc = now;
             current.EndReason = reason;
             db.Update(current);
         }
-
-        EnsureAssignTransition(note.Status, isReassign);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs` around lines
40 - 52, Move EnsureAssignTransition(note.Status, isReassign) before the current
assignment mutation block in the assignment method, so invalid transitions
return without modifying tracked state. Keep the existing current assignment
updates and db.Update(current) unchanged after validation succeeds.

200-279: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce cognitive complexity and de-duplicate the facility-region lookup.

SonarCloud flags this method at complexity 29 vs. the 15 allowed. Beyond the metric, the "fetch a facility's RegionId" query (Lines 230-233 and 258-261) is duplicated verbatim across the Facility and FacilityUnit cases. Extracting each case into a private per-scope-type helper (and sharing the facility-region lookup) would satisfy the Sonar gate and remove the duplication.

♻️ Sketch of the extraction
-    private async Task<bool> IntersectsNoteAsync(IReadOnlyList<UserScopeSnapshot> scopes, OperationalNote note, CancellationToken cancellationToken)
-    {
-        switch (note.ScopeType)
-        {
-            case ScopeType.Region: { /* ... */ }
-            case ScopeType.Facility: { /* ... */ }
-            case ScopeType.FacilityUnit: { /* ... */ }
-            case ScopeType.Global: return scopes.Any(...);
-            case ScopeType.Headquarters: return scopes.Any(...);
-            default: return false;
-        }
-    }
+    private Task<bool> IntersectsNoteAsync(IReadOnlyList<UserScopeSnapshot> scopes, OperationalNote note, CancellationToken cancellationToken) =>
+        note.ScopeType switch
+        {
+            ScopeType.Region => IntersectsRegionAsync(scopes, note, cancellationToken),
+            ScopeType.Facility => IntersectsFacilityAsync(scopes, note, cancellationToken),
+            ScopeType.FacilityUnit => IntersectsFacilityUnitAsync(scopes, note, cancellationToken),
+            ScopeType.Global => Task.FromResult(scopes.Any(s => s.ScopeType == ScopeType.Global)),
+            ScopeType.Headquarters => Task.FromResult(scopes.Any(s => s.ScopeType is ScopeType.Headquarters or ScopeType.Global)),
+            _ => Task.FromResult(false)
+        };
+
+    private async Task<Guid?> GetFacilityRegionIdAsync(Guid facilityId, CancellationToken cancellationToken) =>
+        await db.Facilities.Where(f => f.Id == facilityId).Select(f => (Guid?)f.RegionId).FirstOrDefaultAsync(cancellationToken);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs` around lines
200 - 279, Reduce complexity in IntersectsNoteAsync by extracting the Region,
Facility, and FacilityUnit case logic into focused private helpers, leaving the
main switch as simple dispatch. Introduce one shared private helper for
retrieving a facility’s nullable RegionId and reuse it from the Facility and
FacilityUnit helpers, preserving all existing scope-matching behavior and
cancellation-token propagation.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs`:
- Around line 40-52: Move EnsureAssignTransition(note.Status, isReassign) before
the current assignment mutation block in the assignment method, so invalid
transitions return without modifying tracked state. Keep the existing current
assignment updates and db.Update(current) unchanged after validation succeeds.
- Around line 200-279: Reduce complexity in IntersectsNoteAsync by extracting
the Region, Facility, and FacilityUnit case logic into focused private helpers,
leaving the main switch as simple dispatch. Introduce one shared private helper
for retrieving a facility’s nullable RegionId and reuse it from the Facility and
FacilityUnit helpers, preserving all existing scope-matching behavior and
cancellation-token propagation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d09cb56e-2cbc-42af-8fc6-911f44202e9d

📥 Commits

Reviewing files that changed from the base of the PR and between 6e0b27c and 0ae33d5.

📒 Files selected for processing (19)
  • docs/phase-b1-completion-report.md
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/backend/Baseera.Application/Attachments/AttachmentAppService.cs
  • src/backend/Baseera.Application/Baseera.Application.csproj
  • src/backend/Baseera.Application/Notes/NoteAccessHelper.cs
  • src/backend/Baseera.Application/Notes/NoteAssignmentService.cs
  • src/backend/Baseera.Application/Notes/NoteCommandService.cs
  • src/backend/Baseera.Application/Notes/NoteQueryService.cs
  • src/backend/Baseera.Application/Notes/NoteScopeService.cs
  • src/backend/Baseera.Application/Notes/NoteWorkflowService.cs
  • src/backend/Baseera.Application/Organization/OrganizationService.cs
  • src/backend/Baseera.Infrastructure/Persistence/DatabaseInitializer.cs
  • src/backend/tests/Baseera.IntegrationTests/NotesAdditionalIntegrationTests.cs
  • src/frontend/src/api/client.ts
  • src/frontend/src/notes/NoteForm.tsx
  • src/frontend/src/pages/notes/NoteDetailPage.tsx
  • src/frontend/src/pages/notes/NoteEditPage.test.tsx
  • src/frontend/src/pages/notes/NoteEditPage.tsx
  • src/frontend/src/pages/notes/NotesListPage.tsx
🚧 Files skipped from review as they are similar to previous changes (14)
  • docs/phase-b1-completion-report.md
  • src/backend/Baseera.Application/Notes/NoteAccessHelper.cs
  • src/frontend/src/notes/NoteForm.tsx
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/backend/Baseera.Infrastructure/Persistence/DatabaseInitializer.cs
  • src/frontend/src/pages/notes/NotesListPage.tsx
  • src/frontend/src/pages/notes/NoteEditPage.test.tsx
  • src/frontend/src/pages/notes/NoteEditPage.tsx
  • src/backend/Baseera.Application/Notes/NoteCommandService.cs
  • src/backend/tests/Baseera.IntegrationTests/NotesAdditionalIntegrationTests.cs
  • src/backend/Baseera.Application/Notes/NoteQueryService.cs
  • src/backend/Baseera.Application/Notes/NoteScopeService.cs
  • src/frontend/src/api/client.ts
  • src/backend/Baseera.Application/Notes/NoteWorkflowService.cs

henter36 and others added 8 commits July 19, 2026 17:36
…hmentAction.

Collapse notes list endpoint to a three-parameter AsParameters handler, extract assignee scope intersection helpers under cognitive complexity limits, and replace the nested attachment download ternary with an early-return component.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Application NoteListQuery under [AsParameters] returned HTTP 400; bind via NoteListQueryParams with [FromQuery] and map to NoteListQuery without changing the public query contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
Minimal API treats non-nullable AsParameters properties as required query
keys, which returned HTTP 400 on GET /notes without full filter sets.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Create notes with a future due date then backdate in DB, matching
API validation that rejects past DueAtUtc on create.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/Baseera.Application/Notes/NoteAssignmentService.cs (1)

31-38: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix the validation logic to independently verify provided assignee and department IDs.

The current if/else structure has two critical gaps:

  1. If both are null: The execution falls to the else block and evaluates request.AssignedToDepartmentId!.Value, which throws an unhandled InvalidOperationException (Nullable object must have a value), resulting in an HTTP 500.
  2. If both are provided: The execution validates the user but skips the else block. The unvalidated department ID is then written to the database (at line 58), bypassing the !d.IsDeleted and existence checks entirely.

Ensure both fields are evaluated independently and throw a clear business error if neither is provided.

🛠️ Proposed fix to independently validate IDs
-        if (request.AssignedToUserId.HasValue)
-        {
-            await ValidateAssigneeUserAsync(request.AssignedToUserId.Value, note, cancellationToken);
-        }
-        else
-        {
-            await ValidateAssigneeDepartmentAsync(request.AssignedToDepartmentId!.Value, cancellationToken);
-        }
+        if (!request.AssignedToUserId.HasValue && !request.AssignedToDepartmentId.HasValue)
+        {
+            throw new InvalidOperationException("يجب تحديد مستخدم أو إدارة للتكليف.");
+        }
+
+        if (request.AssignedToUserId.HasValue)
+        {
+            await ValidateAssigneeUserAsync(request.AssignedToUserId.Value, note, cancellationToken);
+        }
+
+        if (request.AssignedToDepartmentId.HasValue)
+        {
+            await ValidateAssigneeDepartmentAsync(request.AssignedToDepartmentId.Value, cancellationToken);
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs` around lines
31 - 38, Update the assignee validation logic in NoteAssignmentService to
evaluate AssignedToUserId and AssignedToDepartmentId independently, validating
each when provided so both IDs cannot bypass checks. When neither ID is
supplied, throw the established clear business/business-validation error instead
of dereferencing a nullable department ID; preserve the existing user and
department validation methods and cancellation flow.
🧹 Nitpick comments (1)
src/backend/Baseera.Application/Notes/NoteAssignmentService.cs (1)

186-190: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant Headquarters scope check.

This manual short-circuit is redundant and can be safely removed because NoteAssigneeScopeIntersection.IntersectsAsync already handles ScopeType.Headquarters internally using the exact same logic (HasHeadquartersScope).

(Note: The Global check above this is still required because IntersectsAsync does not default to accepting global assignees for region/facility notes).

♻️ Proposed refactor
-        if (note.ScopeType == ScopeType.Headquarters &&
-            scopes.Any(s => s.ScopeType is ScopeType.Headquarters or ScopeType.Global))
-        {
-            return;
-        }
-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs` around lines
186 - 190, Remove the manual Headquarters short-circuit in
NoteAssignmentService, specifically the condition checking note.ScopeType and
scopes.Any for Headquarters or Global. Preserve the separate Global handling
required for region/facility notes, and rely on
NoteAssigneeScopeIntersection.IntersectsAsync to apply the existing Headquarters
logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs`:
- Around line 31-38: Update the assignee validation logic in
NoteAssignmentService to evaluate AssignedToUserId and AssignedToDepartmentId
independently, validating each when provided so both IDs cannot bypass checks.
When neither ID is supplied, throw the established clear
business/business-validation error instead of dereferencing a nullable
department ID; preserve the existing user and department validation methods and
cancellation flow.

---

Nitpick comments:
In `@src/backend/Baseera.Application/Notes/NoteAssignmentService.cs`:
- Around line 186-190: Remove the manual Headquarters short-circuit in
NoteAssignmentService, specifically the condition checking note.ScopeType and
scopes.Any for Headquarters or Global. Preserve the separate Global handling
required for region/facility notes, and rely on
NoteAssigneeScopeIntersection.IntersectsAsync to apply the existing Headquarters
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 27c778af-3a76-443f-b87c-dd391db9a2e0

📥 Commits

Reviewing files that changed from the base of the PR and between 0ae33d5 and 46a74b2.

📒 Files selected for processing (10)
  • docs/phase-b1-completion-report.md
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/backend/Baseera.Api/Endpoints/NoteListQueryParams.cs
  • src/backend/Baseera.Application/Notes/NoteAssigneeScopeIntersection.cs
  • src/backend/Baseera.Application/Notes/NoteAssignmentService.cs
  • src/backend/tests/Baseera.IntegrationTests/NotesAdditionalIntegrationTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteAssigneeScopeIntersectionTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteListQueryParamsTests.cs
  • src/frontend/src/pages/notes/NoteDetailPage.test.tsx
  • src/frontend/src/pages/notes/NoteDetailPage.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • docs/phase-b1-completion-report.md
  • src/backend/tests/Baseera.IntegrationTests/NotesAdditionalIntegrationTests.cs
  • src/frontend/src/pages/notes/NoteDetailPage.test.tsx
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/frontend/src/pages/notes/NoteDetailPage.tsx

henter36 and others added 3 commits July 19, 2026 19:17
Reject invalid assignment targets in the application service, validate
workflow before mutating current assignments, drop redundant HQ scope
short-circuit, and map current-assignment unique conflicts to HTTP 409.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud

Copy link
Copy Markdown

@henter36
henter36 merged commit bda6bdd into main Jul 19, 2026
7 checks passed
@henter36
henter36 deleted the phase-b1-notes-core branch July 19, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant