Skip to content

Phase D.5 Facility Resource Readiness Center — Core Assets - #132

Merged
henter36 merged 8 commits into
mainfrom
phase-d5-facility-resource-readiness-core
Jul 25, 2026
Merged

Phase D.5 Facility Resource Readiness Center — Core Assets#132
henter36 merged 8 commits into
mainfrom
phase-d5-facility-resource-readiness-core

Conversation

@henter36

@henter36 henter36 commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the Phase D.5 Facility Resource Readiness Center core for Baseera: operational resource inventory, readiness state, placement/ownership, maintenance, requirements/gaps, and Facility Workspace integration.

Scope delivered

  • Core readiness center for facility resources
  • Vehicles
  • Communications equipment
  • Operational and security equipment (non-weapons)
  • Fixed assets
  • Status, placement, and ownership
  • Maintenance workflow support
  • Requirement and gap tracking
  • Facility Workspace integration
  • EF Core migration, unit/integration tests, and Phase D.5 documentation (including RTL walkthrough screenshots)

Issue links

Does not close #15 or #11.

Notes

  • Frontend test hardening for async notification/workspace assertions is included only to stabilize CI; it is not the primary scope of this PR.

Test plan

  • dotnet build src/backend/Baseera.slnx -c Release
  • Unit tests green
  • Integration tests green with BASEERA_TEST_CONNECTION and Skipped = 0
  • Frontend typecheck / lint / test / build
  • npm audit --audit-level=high
  • NuGet vulnerability gate
  • Migration applies cleanly on latest main
  • Screenshots under docs/screenshots/phase-d5/ present
  • No secrets / mock production data / unintended build artifacts

Summary by CodeRabbit

  • New Features

    • Added a facility Resource Readiness Center with readiness metrics, categories, exceptions, unit distribution, activity timelines, and asset management.
    • Added resource asset creation, maintenance tracking, requirements, status and placement updates, and import preview/confirmation.
    • Added a dedicated resources page and integrated resource readiness into facility workspaces.
    • Added granular permissions, facility scoping, data-quality indicators, and privacy safeguards.
  • Documentation

    • Documented resource APIs, domain concepts, permissions, workflows, migration, performance, security, and scope.
  • Bug Fixes

    • Improved asynchronous test synchronization for notification and workspace rendering.

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

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai

sourcery-ai Bot commented Jul 25, 2026

Copy link
Copy Markdown

🧙 Sourcery has finished reviewing your pull request!


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@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 →

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a facility-scoped resource readiness center spanning domain entities, APIs, authorization, persistence, workspace widgets, a dedicated frontend page, imports, maintenance, readiness calculations, seed data, documentation, and automated validation.

Changes

Phase D.5 resource contracts and persistence

Layer / File(s) Summary
Scope and operational contracts
README.md, docs/phase-d5-*, docs/implementation-plan.md
Defines the Phase D.5 resource scope, domain model, API behavior, permissions, imports, readiness calculations, migration, security, performance, RTL behavior, and test matrix.
Resource domain and persistence
src/backend/Baseera.Domain/Resources/*, src/backend/Baseera.Infrastructure/Persistence/*, src/backend/Baseera.Api/Authorization/*
Adds resource entities, profiles, status history, placements, maintenance, requirements, import batches, EF configuration, migration schema, append-only guards, permissions, role grants, and demo data.

Resource application and workspace flow

Layer / File(s) Summary
Readiness services and API
src/backend/Baseera.Application/Resources/*, src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs, src/backend/Baseera.Application/Abstractions/Abstractions.cs
Adds DTOs, readiness and access policies, facility-scoped queries and commands, import workflows, audit handling, authorization, dependency injection, and REST endpoints.
Facility workspace integration
src/backend/Baseera.Application/Workspaces/*, src/backend/tests/Baseera.UnitTests/Workspaces/*
Adds the resources widget, workspace payload composition, priority/activity drill-downs, data-quality mapping, route targets, and permission-aware workspace tests.

Frontend and validation

Layer / File(s) Summary
Frontend resource experience and validation
src/frontend/src/pages/resources/*, src/frontend/src/pages/workspaces/*, src/frontend/src/api/client.ts, src/frontend/src/App.tsx, src/frontend/src/index.css, src/backend/tests/*/Resources/*
Adds the protected resources route, typed API client, readiness page, asset creation and import forms, workspace rendering, styling, frontend tests, readiness/access policy tests, integration coverage, and asynchronous test synchronization fixes.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FacilityResourcesPage
  participant ResourceApi
  participant ResourceReadinessService
  participant ResourceDatabase
  User->>FacilityResourcesPage: Open facility resources route
  FacilityResourcesPage->>ResourceApi: Request summary and scoped resource data
  ResourceApi->>ResourceReadinessService: Execute authorized facility queries
  ResourceReadinessService->>ResourceDatabase: Read assets, requirements, events, and maintenance
  ResourceDatabase-->>ResourceReadinessService: Return scoped resource records
  ResourceReadinessService-->>ResourceApi: Return readiness payload
  ResourceApi-->>FacilityResourcesPage: Render summary, exceptions, units, and assets
  User->>FacilityResourcesPage: Submit asset or import action
  FacilityResourcesPage->>ResourceApi: Send create or preview/confirm request
  ResourceApi->>ResourceReadinessService: Validate, persist, and audit operation
  ResourceReadinessService->>ResourceDatabase: Save resource state and history
Loading

Possibly related PRs

  • henter36/Baseera#121: Extends the shared workspace drill-down framework used by the new resources widget and route.
  • henter36/Baseera#122: Provides the facility workspace integration points extended by the Phase D.5 resources payload.
  • henter36/Baseera#123: Provides the command-center and context-panel frontend structure used by the resource readiness workspace rendering.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR delivers the facility-level core assets slice, but #15 also requires personnel, weapons, and facility→region→HQ aggregation, which remain out of scope. Extend the resource center to cover the remaining #15 categories and aggregation levels, or split this PR and relink the issue to the implemented subset.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed No clearly unrelated code changes are present; the extra docs and test hardening support the Phase D.5 resource-center scope.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: Phase D.5 core assets for the facility resource readiness center.
✨ 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-d5-facility-resource-readiness-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.

@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.

Hey - I've left some high level feedback:

  • In ObservationWorkspacePage test, consider replacing await waitFor(() => expect(workspace).toHaveBeenCalled()) with an assertion on the rendered UI state (e.g., presence of the card) to reduce coupling to the implementation detail of the workspace mock.
  • The explicit timeout: 5_000 passed to findByRole may be better extracted into a shared test utility or constant so timeout behavior is consistent and easier to adjust across tests.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `ObservationWorkspacePage` test, consider replacing `await waitFor(() => expect(workspace).toHaveBeenCalled())` with an assertion on the rendered UI state (e.g., presence of the card) to reduce coupling to the implementation detail of the `workspace` mock.
- The explicit `timeout: 5_000` passed to `findByRole` may be better extracted into a shared test utility or constant so timeout behavior is consistent and easier to adjust across tests.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@henter36 henter36 changed the title fix: stabilize flaky frontend test assertions Phase D.5 Facility Resource Readiness Center — Core Assets Jul 25, 2026
henter36 and others added 2 commits July 25, 2026 16:33
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.

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (2)
src/backend/Baseera.Application/Workspaces/FacilityWorkspaceReadService.cs (1)

273-284: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Domain-quality insert order breaks when Occupancy is absent but Resources is present.

domains.Insert(6, ...) for Resources assumes the prior Insert(5, OccupancyDomain(...)) always ran. If a user lacks OccupancyViewSummary (so metrics.Occupancy is null and the index-5 insert is skipped) but has ResourcesViewSummary, the Resources domain lands after the original "risks" slot instead of right after "forms"/before "incidents" — producing an inconsistent domain order for that permission combination.

🐛 Proposed fix using a running insert index
-            if (metrics.Occupancy is not null)
-            {
-                domains.Insert(5, OccupancyDomain(metrics.Occupancy));
-            }
-            if (metrics.Resources is not null)
-            {
-                domains.Insert(6, ResourcesDomain(metrics.Resources));
-            }
-            else
-            {
-                domains.Insert(6, MissingDomain("resources", "الموارد والجاهزية", "لا يملك المستخدم صلاحية عرض الموارد أو لم تُحمّل بيانات المجال.", "`#15`"));
-            }
+            var insertIndex = 5;
+            if (metrics.Occupancy is not null)
+            {
+                domains.Insert(insertIndex++, OccupancyDomain(metrics.Occupancy));
+            }
+            domains.Insert(
+                insertIndex,
+                metrics.Resources is not null
+                    ? ResourcesDomain(metrics.Resources)
+                    : MissingDomain("resources", "الموارد والجاهزية", "لا يملك المستخدم صلاحية عرض الموارد أو لم تُحمّل بيانات المجال.", "`#15`"));
🤖 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/Workspaces/FacilityWorkspaceReadService.cs`
around lines 273 - 284, Update the domain insertion logic in
FacilityWorkspaceReadService so the Resources and missing-Resources branches use
a running insertion index rather than hard-coded index 6, incrementing it only
when the Occupancy domain is inserted. Preserve the intended order of forms,
occupancy when available, resources, and incidents for all permission
combinations, including when Occupancy is absent.
src/frontend/src/pages/workspaces/FacilityWorkspacePage.tsx (1)

640-680: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Resource panels fall through to the generic "domain not implemented" gap panel.

PanelDetail has no branch for the new vehicle/weapon/communication-device/equipment panel types produced by resourcePanelType, panelForPriorityItem, and panelForActivityItem. They all fall through to DomainGapPanel, whose copy explicitly states no independent domain model exists — but Resources now has a full domain/API. Clicking a resource category rail, exception, priority item, or activity item opens this misleading "not implemented" panel instead of real resource details, even though a full FacilityResourcesPage exists behind the "فتح الصفحة الكاملة" link.

Separately, category-rail/exception fallback entityIds use domain-resources-${resourceTypeCode}, but findPanelSummary's domain lookup only matches on the exact domain key "resources", so summary resolves to undefined for these clicks — reinforcing the generic/empty panel.

Consider adding a dedicated resource panel (fetching asset/exception detail similarly to NotePanel/CorrectiveActionPanel) rather than reusing DomainGapPanel for an implemented domain.

Also applies to: 1108-1174, 1536-1566, 1649-1654

🤖 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/workspaces/FacilityWorkspacePage.tsx` around lines 640
- 680, Update PanelDetail to handle vehicle, weapon, communication-device, and
equipment panel types with a dedicated resource-detail panel instead of
DomainGapPanel, reusing the established detail-fetching patterns from NotePanel
and CorrectiveActionPanel. Update findPanelSummary and the resource panel
creators resourcePanelType, panelForPriorityItem, and panelForActivityItem so
domain-resources-${resourceTypeCode} entity IDs resolve against the resources
domain key. Preserve the existing fallback only for genuinely unsupported panel
types.
🟡 Minor comments (6)
src/backend/Baseera.Application/Resources/ResourceServices.cs-689-723 (1)

689-723: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Category readiness passes StaleRecords where the policy expects MissingDataRecords. Line 705 feeds stale counts into ResourceReadinessInputs.MissingDataRecords, conflating "not recently verified" with "incomplete data". The resulting DataCompletenessRate happens to be discarded for categories today, so this is latent rather than user-visible — but it will silently produce wrong completeness the moment the field is surfaced.

🤖 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/Resources/ResourceServices.cs` around lines
689 - 723, The BuildCategory method incorrectly passes row.StaleRecords into the
ResourceReadinessInputs.MissingDataRecords field. Update this argument to use
the corresponding MissingDataRecords value from ResourceCategoryCounts,
preserving StaleRecords for freshness status and confidence calculations.
src/backend/Baseera.Application/Resources/ResourceServices.cs-264-279 (1)

264-279: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Gap is hard-coded to 0 for every unit. The DTO advertises a per-unit gap and the frontend renders it, but ResourceRequirement.FacilityUnitId is never consulted here, so the UI will always show zero unit-level shortfall — indistinguishable from "fully covered". Either compute it from unit-scoped requirements or drop the field from the payload until it's implemented.

🤖 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/Resources/ResourceServices.cs` around lines
264 - 279, The ResourceUnitDistributionDto mapping currently hard-codes Gap to
zero and ignores ResourceRequirement.FacilityUnitId. Update the surrounding
resource distribution method to calculate each unit’s gap from requirements
scoped to row.OperationalFacilityUnitId, or remove Gap from the DTO payload
until that calculation is implemented; do not continue sending a misleading zero
value.
src/backend/Baseera.Application/Resources/ResourceServices.cs-528-559 (1)

528-559: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Backdated placements can produce inverted intervals. Active placements are closed with EffectiveToUtc = request.EffectiveFromUtc without checking that the new EffectiveFromUtc is at or after the existing EffectiveFromUtc, and the new row's EffectiveToUtc is never validated against its own EffectiveFromUtc. Both cases persist history where EffectiveToUtc < EffectiveFromUtc, which breaks any "active placement at time T" query.

🤖 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/Resources/ResourceServices.cs` around lines
528 - 559, The PlaceAssetAsync method must reject or otherwise prevent inverted
placement intervals. Before closing active placements, validate that
request.EffectiveFromUtc is not earlier than each existing placement’s
EffectiveFromUtc, and validate the new placement’s EffectiveToUtc is null or at
least request.EffectiveFromUtc; preserve the existing placement update and
creation flow only for valid intervals.
src/backend/Baseera.Infrastructure/Persistence/DatabaseInitializer.cs-930-938 (1)

930-938: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Demo resource seeding assumes FacilityA1 units exist. The guard only checks the facility row and the absence of resource assets, yet the seeds hard-reference SeedIds.FacilityA1UnitNorth/South/Medical. EnsureDemoOccupancyAsync only inserts those units when no units exist for the facility, so a database with the facility but different units will hit an FK violation on SaveChangesAsync and fail startup seeding. Consider verifying unit presence (or resolving unit ids from the DB) before seeding placements/profiles.

Also applies to: 944-979

🤖 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/DatabaseInitializer.cs` around
lines 930 - 938, Update the guard in the demo resource seeding flow around
EnsureDemoOccupancyAsync to verify that the required FacilityA1
units—SeedIds.FacilityA1UnitNorth, SeedIds.FacilityA1UnitSouth, and
SeedIds.FacilityA1UnitMedical—exist before inserting placements or profiles.
Preserve the existing early returns for a missing facility or existing resource
assets, and ensure seeding skips or safely resolves unit IDs when those units
are unavailable.
src/backend/Baseera.Application/Resources/ResourceServices.cs-591-619 (1)

591-619: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

No overlap guard lets requirement baselines double-count. GetSummaryAsync sums RequiredQuantity over every active requirement (Lines 121-122), so recording a second requirement for the same (ResourceType, ResourceCategory, FacilityUnitId) with an overlapping effective window inflates Required and Gap rather than superseding the previous baseline. Issue #15 explicitly calls for duplicate prevention here — consider closing the prior row's EffectiveToUtc or rejecting overlaps.

🤖 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/Resources/ResourceServices.cs` around lines
591 - 619, Add overlap protection to RecordRequirementAsync for the same
ResourceType, ResourceCategory, and FacilityUnitId before creating the new
ResourceRequirement. Query existing requirements with intersecting effective
windows and either reject the new request or close the prior row’s
EffectiveToUtc, ensuring GetSummaryAsync cannot double-count active baselines.
src/backend/Baseera.Application/Resources/ResourceReadinessPolicy.cs-104-122 (1)

104-122: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restrict transitions out of terminal resource statuses. CanTransition only blocks Retired → non-Unknown, so retired assets can be moved to Unknown and then elsewhere, and Lost exits are always allowed. This conflicts with the state machine’s terminal statuses; Lost, Retired, Transferred, and Unknown should either explicitly transition only to approved statuses or be treated as terminal.

🤖 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/Resources/ResourceReadinessPolicy.cs` around
lines 104 - 122, Update ResourceReadinessPolicy.CanTransition to enforce
terminal-state rules for Lost, Retired, Transferred, and Unknown: prevent
unapproved transitions out of each terminal status, including the
Retired-to-Unknown escape path, while preserving same-status transitions and any
explicitly approved destinations defined by the state machine.
🧹 Nitpick comments (10)
docs/phase-d5-resource-test-matrix.md (1)

12-18: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Update planned coverage entries to point at existing integration coverage.

ResourceReadinessIntegrationTests already covers facility scope, unauthorized Forbidden, facility out-of-scope NotFound, workspace widget visibility, authorized asset creation/auditing, and EF migration model verification. Replacing the stale “Planned CI/integration coverage” bullets with these existing tests or moving them to “Implemented automated coverage” keeps this matrix actionable.

🤖 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 `@docs/phase-d5-resource-test-matrix.md` around lines 12 - 18, Replace the
stale “Planned CI/integration coverage” entries in the resource test matrix with
an “Implemented automated coverage” section that references
ResourceReadinessIntegrationTests, including its facility-scope authorization
responses, workspace widget visibility, authorized asset creation/auditing, and
EF migration model verification coverage.
src/backend/Baseera.Application/Resources/ResourceServices.cs (4)

626-638: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Redundant scope check per import call. PreviewAsync/ConfirmAsync call EnsureFacilityVisibleAsync and then ValidateImportAsync immediately repeats it (Line 799), doubling the facility lookup. Drop it from the public entry points since the shared helper already returns the scope info.

Also applies to: 792-799

🤖 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/Resources/ResourceServices.cs` around lines
626 - 638, Remove the redundant EnsureFacilityVisibleAsync calls from
PreviewAsync and ConfirmAsync, leaving their permission checks and
ValidateImportAsync invocations intact. Preserve the shared facility-scope
validation performed inside ValidateImportAsync.

65-90: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Repeated permission lookups obscure the gating rule. HasPermission(ResourcesViewAssets) is evaluated three times; hoisting it into a local (var canViewAssets = ...) makes the payload-shaping policy readable in one glance.

🤖 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/Resources/ResourceServices.cs` around lines
65 - 90, In GetWorkspacePayloadAsync, evaluate
currentUser.HasPermission(PermissionCodes.ResourcesViewAssets) once into a local
canViewAssets variable, then reuse it for the categories, exceptions, and
distribution gates while preserving the existing permission behavior.

165-171: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Awaiting a second query inside the object initializer hides an extra round trip. DataEffectiveAtUtc re-scans the facility's assets after the aggregate query. Fold MAX(LastVerifiedAtUtc) into the existing ResourceStatusCounts projection, or at least hoist the await above the initializer for readability.

🤖 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/Resources/ResourceServices.cs` around lines
165 - 171, Update the resource status construction around the
ResourceStatusCounts query and object initializer so DataEffectiveAtUtc does not
await a separate AssetsInFacility query inside the initializer. Prefer including
MAX(LastVerifiedAtUtc) in the existing ResourceStatusCounts projection and reuse
that result; otherwise execute the asset query before the initializer and assign
the captured value.

43-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

ResourceReadinessOptions is instantiated inline, so none of it is configurable. The class is shaped like an options type (init setters, defaults) but new() bypasses configuration entirely. Either register it with IOptions<ResourceReadinessOptions> and inject, or make the values const to signal they aren't tunable.

Also applies to: 63-63

🤖 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/Resources/ResourceServices.cs` around lines
43 - 49, Make ResourceReadinessOptions genuinely configurable by registering it
through the application configuration and injecting
IOptions<ResourceReadinessOptions> wherever it is consumed, replacing inline new
ResourceReadinessOptions() construction with the injected values. Preserve the
existing defaults as configuration fallbacks and update all affected usages,
including the additional location noted in the comment.
src/backend/Baseera.Infrastructure/Persistence/DatabaseInitializer.cs (1)

255-272: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

DRY: resourceManager restates every code in resourceSummary. Compose it instead so the two can't drift.

♻️ Suggested composition
-        string[] resourceManager =
-        [
-            PermissionCodes.ResourcesViewSummary,
-            PermissionCodes.ResourcesViewAssets,
-            PermissionCodes.ResourcesViewVehicles,
-            PermissionCodes.ResourcesViewCommunicationDevices,
-            PermissionCodes.ResourcesViewEquipment,
-            PermissionCodes.ResourcesViewFacilityAssets,
-            PermissionCodes.ResourcesManageAssets,
+        string[] resourceManager =
+        [
+            ..resourceSummary,
+            PermissionCodes.ResourcesManageAssets,
             PermissionCodes.ResourcesManagePlacements,
             PermissionCodes.ResourcesManageStatus,
-            PermissionCodes.ResourcesViewMaintenance,
             PermissionCodes.ResourcesManageMaintenance,
-            PermissionCodes.ResourcesViewRequirements,
             PermissionCodes.ResourcesManageRequirements,
             PermissionCodes.ResourcesImport,
             PermissionCodes.ResourcesReconcile
         ];
🤖 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/DatabaseInitializer.cs` around
lines 255 - 272, Update the resourceManager initialization in
DatabaseInitializer to compose its values from the existing resourceSummary
collection instead of duplicating each PermissionCodes entry. Preserve the
intended resource manager contents while ensuring future changes to
resourceSummary are reflected automatically.
docs/phase-d5-resource-readiness-calculation.md (1)

7-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify the self-referential Available definition. Line 8 reads as Available = Available + Standby, which is circular. It describes the availability-rate numerator, and the denominator (TotalRegistered - Retired - Transferred) plus the null case when the denominator is zero are worth spelling out, per ResourceReadinessPolicy.Calculate (src/backend/Baseera.Application/Resources/ResourceReadinessPolicy.cs:45-52).

📝 Suggested wording
 - Operational = Available + InUse + Standby + Reserved.
-- Available = Available + Standby.
-- Retired and Transferred are excluded from the operational availability denominator.
+- Availability rate = (Available + Standby) / (TotalRegistered - Retired - Transferred), and is null when that denominator is zero.
+- Retired and Transferred are excluded from the availability denominator.
🤖 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 `@docs/phase-d5-resource-readiness-calculation.md` around lines 7 - 13, Clarify
the readiness documentation’s self-referential Available definition by
describing it as the availability-rate numerator: Available plus Standby. Add
the denominator as TotalRegistered minus Retired and Transferred, and state that
the readiness rate is null when this denominator is zero, matching
ResourceReadinessPolicy.Calculate.
src/backend/Baseera.Domain/Resources/ResourceEntities.cs (1)

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

Prefer injected time over DateTimeOffset.UtcNow defaults. Both ResourceStatusEvent.RecordedAtUtc and ResourceImportBatch.SubmittedAtUtc default to ambient wall-clock time, while the service layer consistently uses TimeProvider (ResourceServices.cs:446, 858). Any write path that forgets to set these gets non-deterministic, untestable timestamps.

Also applies to: 475-475

🤖 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.Domain/Resources/ResourceEntities.cs` at line 349, Remove
the ambient DateTimeOffset.UtcNow property initializers from
ResourceStatusEvent.RecordedAtUtc and ResourceImportBatch.SubmittedAtUtc. Ensure
each write path assigns timestamps through the existing injected TimeProvider in
the relevant ResourceServices methods, including the paths around the
status-event and import-batch creation flows, while preserving explicit
caller-provided values where applicable.
src/backend/Baseera.Application/Resources/ResourceReadinessPolicy.cs (1)

30-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

IsOperational/IsAvailable/IsInScopeDenominator duplicate the arithmetic in Calculate. The predicates define the operational and in-scope sets, but Calculate re-derives them by summing individual fields (Lines 44-45). Adding a new ResourceStatus means updating both places plus the SQL-side counts in ResourceServices.GetSummaryAsync. Worth a comment tying them together, or deriving ResourceReadinessInputs from the predicates.

🤖 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/Resources/ResourceReadinessPolicy.cs` around
lines 30 - 61, The status predicates and Calculate use separate definitions of
operational and in-scope resources, so they can diverge when statuses change.
Update the ResourceReadinessPolicy implementation to derive or validate
Calculate’s counts through IsOperational, IsAvailable, and IsInScopeDenominator,
and add a concise comment documenting that ResourceReadinessInputs and
ResourceServices.GetSummaryAsync must use the same status sets.
src/frontend/src/pages/resources/FacilityResourcesPage.tsx (1)

38-51: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant refetch of unfiltered data on every category filter click.

summary, categories, and exceptions don't depend on resourceType, but including it in the shared queryKey forces all four calls to re-run whenever the user clicks a category rail button (which only changes the assets filter).

♻️ Suggested split
-  const query = useQuery({
-    queryKey: ['resources-admin', facilityId, resourceType],
-    queryFn: async () => {
-      const filters = resourceType ? { resourceType } : {}
-      const [summary, categories, exceptions, assets] = await Promise.all([
-        api.resources.summary(facilityId!),
-        canViewAssets ? api.resources.categories(facilityId!) : Promise.resolve([]),
-        canViewAssets ? api.resources.exceptions(facilityId!, 20) : Promise.resolve([]),
-        canViewAssets ? api.resources.assets(facilityId!, filters) : Promise.resolve([]),
-      ])
-      return { summary, categories, exceptions, assets }
-    },
-    enabled: canView && Boolean(facilityId),
-  })
+  const baseQuery = useQuery({
+    queryKey: ['resources-admin', facilityId],
+    queryFn: async () => {
+      const [summary, categories, exceptions] = await Promise.all([
+        api.resources.summary(facilityId!),
+        canViewAssets ? api.resources.categories(facilityId!) : Promise.resolve([]),
+        canViewAssets ? api.resources.exceptions(facilityId!, 20) : Promise.resolve([]),
+      ])
+      return { summary, categories, exceptions }
+    },
+    enabled: canView && Boolean(facilityId),
+  })
+  const assetsQuery = useQuery({
+    queryKey: ['resources-admin-assets', facilityId, resourceType],
+    queryFn: () => api.resources.assets(facilityId!, resourceType ? { resourceType } : {}),
+    enabled: canView && canViewAssets && Boolean(facilityId),
+  })
🤖 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/resources/FacilityResourcesPage.tsx` around lines 38 -
51, Split the shared useQuery in FacilityResourcesPage so resourceType only keys
and refetches the assets request, while summary, categories, and exceptions use
a stable facility-scoped query independent of the category filter. Preserve the
existing canViewAssets gating and return the same data needed by the page,
avoiding refetches of unfiltered resources when the category rail changes.
🤖 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 `@docs/phase-d5-resource-api-contract.md`:
- Around line 10-11: Update the documented authorization contract for the
`/assets` and `/assets/{assetId}` routes so access is checked against the
resolved resource type’s specific permission (`Resources.ViewVehicles`,
`Resources.ViewCommunications`, `Resources.ViewEquipment`, or
`Resources.ViewFacilityAssets`) rather than only `Resources.ViewAssets`.
Alternatively, explicitly define `Resources.ViewAssets` as the sole all-category
permission and remove the unused category-specific permission contract.

In `@src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs`:
- Around line 283-289: Update the asset lookup handler in the resources.MapGet
endpoint to inspect the result of service.GetAssetAsync and return
Results.NotFound() when it is null; otherwise preserve the existing Results.Ok
response for found assets, matching the neighboring get-by-id endpoints.

In `@src/backend/Baseera.Application/Abstractions/Abstractions.cs`:
- Around line 91-100: The nine resource DbSet properties in
IBaseeraDbContext—ResourceAssets, VehicleProfiles, CommunicationDeviceProfiles,
EquipmentProfiles, FacilityAssetProfiles, ResourceStatusEvents,
ResourcePlacements, MaintenanceWorkOrders, and ResourceRequirements—must be
abstract rather than defaulting to empty queryables, so implementations are
required to provide them. Update all empty test doubles implementing
IBaseeraDbContext to explicitly define these properties with the appropriate
empty queryable values; leave ResourceImportBatches unchanged unless it is also
part of the intended nine.

In `@src/backend/Baseera.Application/Resources/ResourceServices.cs`:
- Around line 564-589: The CreateWorkOrderAsync method must stop deriving
WorkOrderNumber from the global MaintenanceWorkOrders count. Add a
maintenance-work-order sequence allocator alongside the existing
IBaseeraDbContext sequence methods, use it when constructing WorkOrderNumber,
and add a unique database index for the generated number to prevent collisions
under concurrent creation.
- Around line 849-869: Update the import confirmation flow containing the
`ResourceImportBatch` insertion to first look up an existing confirmed batch by
`(FacilityId, SourceSystem, SourceReference, FileHash)`. If found, return the
prior confirmed result before inserting assets or creating a new batch;
otherwise preserve the existing validation, insertion, save, and audit flow.
- Around line 307-321: Update ListAssetsAsync and its /assets endpoint to
enforce the resource-specific permissions before querying or projecting assets.
Require the appropriate vehicle, communication-device, equipment, or
facility-asset permission based on resourceType, while preserving
ResourcesViewAssets as the base permission and preventing unauthorized sensitive
asset fields from being returned.
- Around line 436-444: Align the resource import flow’s duplicate validation
with the organization-scoped unique constraint by reusing the same creation
validation for preview and insert, rather than facility-scoped OrdinalIgnoreCase
matching. Update the ResourceAssets AnyAsync check accordingly, and catch
DataError from SaveChangesAsync to translate organization-level duplicates and
race conditions into the existing duplicate-violation response.

In `@src/backend/Baseera.Application/Workspaces/FacilityWorkspaceReadService.cs`:
- Around line 160-163: Update the resource item construction in the relevant
branches of the workspace read service, including
BuildResourcePriorityItemsAsync and the locations around the other referenced
blocks, so ResourceTarget uses the permission that granted the item’s caller
context (ResourcesViewSummary or ResourcesViewMaintenance) rather than always
emitting ResourcesViewAssets. Mirror OccupancyTarget’s context-aware permission
handling and preserve the existing resource list/detail permission checks.

In `@src/backend/Baseera.Infrastructure/Persistence/BaseeraDbContext.cs`:
- Around line 295-297: Update the model configuration in BaseeraDbContext to add
query filters for resource profiles, status events, placements, and maintenance
work orders that exclude records whose parent ResourceAsset is soft-deleted.
Preserve existing soft-delete filters and ensure administrative or audit queries
can still access these records via IgnoreQueryFilters().

In
`@src/backend/Baseera.Infrastructure/Persistence/Configurations/ResourceConfigurations.cs`:
- Around line 105-117: The ResourceStatusEvent configuration lacks append-only
enforcement. Add a ResourceStatusEventAppendOnlyGuard that rejects Modified and
Deleted entries, then invoke it from BaseeraDbContext.EnforceAppendOnlyGuards()
while preserving allowed Added and Unchanged states.

In
`@src/backend/Baseera.Infrastructure/Persistence/Migrations/20260725111037_PhaseD5ResourceReadinessCore.cs`:
- Around line 86-114: Add the required HasOne<Facility>() and HasOne<User>()
relationships to ResourceImportBatchConfiguration, including their foreign-key
properties and delete behavior, then regenerate the migration and snapshot.
Update 20260725111037_PhaseD5ResourceReadinessCore.cs lines 86-114 and
BaseeraDbContextModelSnapshot.cs lines 5031-5111 so ResourceImportBatches
includes matching foreign-key constraints for FacilityId and SubmittedByUserId.

In
`@src/backend/Baseera.Infrastructure/Persistence/Migrations/20260725111037_PhaseD5ResourceReadinessCore.Designer.cs`:
- Around line 5110-5113: Update the CK_ResourceImportBatches_Counts check
constraint in the ResourceImportBatches table mapping to enforce the full import
contract: require component counts to equal TotalRows and ensure AppliedRows
does not exceed ValidRows, while retaining all non-negative checks.
- Around line 5273-5279: Update the ResourceRequirements model configuration
represented by the migration designer so the facility/unit/resource
type/category identity cannot have duplicate active requirements under the
intended effective-period semantics. Replace or supplement the non-unique index
involving FacilityId, FacilityUnitId, ResourceType, ResourceCategory, and
EffectiveFromUtc with the appropriate unique index or equivalent database
constraint, and ensure the migration applies this enforcement.
- Around line 5034-5056: Update the ResourceImportBatch model configuration to
define a required relationship from ResourceImportBatch.FacilityId to the
Facility entity, using the existing facility key and appropriate delete
behavior. Ensure the generated migration and model snapshot include the
corresponding foreign key constraint.
- Around line 4976-4980: Update the EF relationship mappings containing
OperationalFacilityId and OperationalFacilityUnitId so the database enforces
that the selected unit belongs to the selected facility. Define a composite
principal key or equivalent constraint on the facility-unit relationship and use
composite foreign keys for every affected entity configuration, including all
repeated mappings in this migration; do not leave the two IDs enforced
independently.

---

Outside diff comments:
In `@src/backend/Baseera.Application/Workspaces/FacilityWorkspaceReadService.cs`:
- Around line 273-284: Update the domain insertion logic in
FacilityWorkspaceReadService so the Resources and missing-Resources branches use
a running insertion index rather than hard-coded index 6, incrementing it only
when the Occupancy domain is inserted. Preserve the intended order of forms,
occupancy when available, resources, and incidents for all permission
combinations, including when Occupancy is absent.

In `@src/frontend/src/pages/workspaces/FacilityWorkspacePage.tsx`:
- Around line 640-680: Update PanelDetail to handle vehicle, weapon,
communication-device, and equipment panel types with a dedicated resource-detail
panel instead of DomainGapPanel, reusing the established detail-fetching
patterns from NotePanel and CorrectiveActionPanel. Update findPanelSummary and
the resource panel creators resourcePanelType, panelForPriorityItem, and
panelForActivityItem so domain-resources-${resourceTypeCode} entity IDs resolve
against the resources domain key. Preserve the existing fallback only for
genuinely unsupported panel types.

---

Minor comments:
In `@src/backend/Baseera.Application/Resources/ResourceReadinessPolicy.cs`:
- Around line 104-122: Update ResourceReadinessPolicy.CanTransition to enforce
terminal-state rules for Lost, Retired, Transferred, and Unknown: prevent
unapproved transitions out of each terminal status, including the
Retired-to-Unknown escape path, while preserving same-status transitions and any
explicitly approved destinations defined by the state machine.

In `@src/backend/Baseera.Application/Resources/ResourceServices.cs`:
- Around line 689-723: The BuildCategory method incorrectly passes
row.StaleRecords into the ResourceReadinessInputs.MissingDataRecords field.
Update this argument to use the corresponding MissingDataRecords value from
ResourceCategoryCounts, preserving StaleRecords for freshness status and
confidence calculations.
- Around line 264-279: The ResourceUnitDistributionDto mapping currently
hard-codes Gap to zero and ignores ResourceRequirement.FacilityUnitId. Update
the surrounding resource distribution method to calculate each unit’s gap from
requirements scoped to row.OperationalFacilityUnitId, or remove Gap from the DTO
payload until that calculation is implemented; do not continue sending a
misleading zero value.
- Around line 528-559: The PlaceAssetAsync method must reject or otherwise
prevent inverted placement intervals. Before closing active placements, validate
that request.EffectiveFromUtc is not earlier than each existing placement’s
EffectiveFromUtc, and validate the new placement’s EffectiveToUtc is null or at
least request.EffectiveFromUtc; preserve the existing placement update and
creation flow only for valid intervals.
- Around line 591-619: Add overlap protection to RecordRequirementAsync for the
same ResourceType, ResourceCategory, and FacilityUnitId before creating the new
ResourceRequirement. Query existing requirements with intersecting effective
windows and either reject the new request or close the prior row’s
EffectiveToUtc, ensuring GetSummaryAsync cannot double-count active baselines.

In `@src/backend/Baseera.Infrastructure/Persistence/DatabaseInitializer.cs`:
- Around line 930-938: Update the guard in the demo resource seeding flow around
EnsureDemoOccupancyAsync to verify that the required FacilityA1
units—SeedIds.FacilityA1UnitNorth, SeedIds.FacilityA1UnitSouth, and
SeedIds.FacilityA1UnitMedical—exist before inserting placements or profiles.
Preserve the existing early returns for a missing facility or existing resource
assets, and ensure seeding skips or safely resolves unit IDs when those units
are unavailable.

---

Nitpick comments:
In `@docs/phase-d5-resource-readiness-calculation.md`:
- Around line 7-13: Clarify the readiness documentation’s self-referential
Available definition by describing it as the availability-rate numerator:
Available plus Standby. Add the denominator as TotalRegistered minus Retired and
Transferred, and state that the readiness rate is null when this denominator is
zero, matching ResourceReadinessPolicy.Calculate.

In `@docs/phase-d5-resource-test-matrix.md`:
- Around line 12-18: Replace the stale “Planned CI/integration coverage” entries
in the resource test matrix with an “Implemented automated coverage” section
that references ResourceReadinessIntegrationTests, including its facility-scope
authorization responses, workspace widget visibility, authorized asset
creation/auditing, and EF migration model verification coverage.

In `@src/backend/Baseera.Application/Resources/ResourceReadinessPolicy.cs`:
- Around line 30-61: The status predicates and Calculate use separate
definitions of operational and in-scope resources, so they can diverge when
statuses change. Update the ResourceReadinessPolicy implementation to derive or
validate Calculate’s counts through IsOperational, IsAvailable, and
IsInScopeDenominator, and add a concise comment documenting that
ResourceReadinessInputs and ResourceServices.GetSummaryAsync must use the same
status sets.

In `@src/backend/Baseera.Application/Resources/ResourceServices.cs`:
- Around line 626-638: Remove the redundant EnsureFacilityVisibleAsync calls
from PreviewAsync and ConfirmAsync, leaving their permission checks and
ValidateImportAsync invocations intact. Preserve the shared facility-scope
validation performed inside ValidateImportAsync.
- Around line 65-90: In GetWorkspacePayloadAsync, evaluate
currentUser.HasPermission(PermissionCodes.ResourcesViewAssets) once into a local
canViewAssets variable, then reuse it for the categories, exceptions, and
distribution gates while preserving the existing permission behavior.
- Around line 165-171: Update the resource status construction around the
ResourceStatusCounts query and object initializer so DataEffectiveAtUtc does not
await a separate AssetsInFacility query inside the initializer. Prefer including
MAX(LastVerifiedAtUtc) in the existing ResourceStatusCounts projection and reuse
that result; otherwise execute the asset query before the initializer and assign
the captured value.
- Around line 43-49: Make ResourceReadinessOptions genuinely configurable by
registering it through the application configuration and injecting
IOptions<ResourceReadinessOptions> wherever it is consumed, replacing inline new
ResourceReadinessOptions() construction with the injected values. Preserve the
existing defaults as configuration fallbacks and update all affected usages,
including the additional location noted in the comment.

In `@src/backend/Baseera.Domain/Resources/ResourceEntities.cs`:
- Line 349: Remove the ambient DateTimeOffset.UtcNow property initializers from
ResourceStatusEvent.RecordedAtUtc and ResourceImportBatch.SubmittedAtUtc. Ensure
each write path assigns timestamps through the existing injected TimeProvider in
the relevant ResourceServices methods, including the paths around the
status-event and import-batch creation flows, while preserving explicit
caller-provided values where applicable.

In `@src/backend/Baseera.Infrastructure/Persistence/DatabaseInitializer.cs`:
- Around line 255-272: Update the resourceManager initialization in
DatabaseInitializer to compose its values from the existing resourceSummary
collection instead of duplicating each PermissionCodes entry. Preserve the
intended resource manager contents while ensuring future changes to
resourceSummary are reflected automatically.

In `@src/frontend/src/pages/resources/FacilityResourcesPage.tsx`:
- Around line 38-51: Split the shared useQuery in FacilityResourcesPage so
resourceType only keys and refetches the assets request, while summary,
categories, and exceptions use a stable facility-scoped query independent of the
category filter. Preserve the existing canViewAssets gating and return the same
data needed by the page, avoiding refetches of unfiltered resources when the
category rail changes.
🪄 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: 7b9c35e1-cf46-43d1-bf6b-155cc62ddcfb

📥 Commits

Reviewing files that changed from the base of the PR and between c45f931 and c51865a.

⛔ Files ignored due to path filters (17)
  • docs/screenshots/phase-d5/desktop-communications.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-context-panel.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-critical-resource.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-equipment.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-facility-assets.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-maintenance.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-requirement-gaps.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-resources-overview.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-stale-data.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/desktop-vehicles.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/empty-state.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/import-preview.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/mobile-asset-detail.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/mobile-overview.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/partial-state.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/tablet-overview.png is excluded by !**/*.png
  • docs/screenshots/phase-d5/validation-errors.png is excluded by !**/*.png
📒 Files selected for processing (52)
  • README.md
  • docs/implementation-plan.md
  • docs/permissions-matrix.md
  • docs/phase-d5-resource-api-contract.md
  • docs/phase-d5-resource-completion-report.md
  • docs/phase-d5-resource-domain-model.md
  • docs/phase-d5-resource-import-contract.md
  • docs/phase-d5-resource-maintenance-workflow.md
  • docs/phase-d5-resource-migration.md
  • docs/phase-d5-resource-performance.md
  • docs/phase-d5-resource-permissions.md
  • docs/phase-d5-resource-placement-and-ownership.md
  • docs/phase-d5-resource-readiness-calculation.md
  • docs/phase-d5-resource-readiness-current-state-analysis.md
  • docs/phase-d5-resource-readiness-scope.md
  • docs/phase-d5-resource-rtl-walkthrough.md
  • docs/phase-d5-resource-security.md
  • docs/phase-d5-resource-source-of-truth.md
  • docs/phase-d5-resource-status-state-machine.md
  • docs/phase-d5-resource-test-matrix.md
  • src/backend/Baseera.Api/Authorization/AuthorizationExtensions.cs
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/backend/Baseera.Application/Abstractions/Abstractions.cs
  • src/backend/Baseera.Application/DependencyInjection/ApplicationServiceCollectionExtensions.cs
  • src/backend/Baseera.Application/Resources/ResourceDtos.cs
  • src/backend/Baseera.Application/Resources/ResourceReadinessPolicy.cs
  • src/backend/Baseera.Application/Resources/ResourceServices.cs
  • src/backend/Baseera.Application/Workspaces/FacilityWorkspaceDefinitions.cs
  • src/backend/Baseera.Application/Workspaces/FacilityWorkspaceDtos.cs
  • src/backend/Baseera.Application/Workspaces/FacilityWorkspaceReadService.cs
  • src/backend/Baseera.Application/Workspaces/FacilityWorkspaceWidgetProviders.cs
  • src/backend/Baseera.Domain/Identity/IdentityEntities.cs
  • src/backend/Baseera.Domain/Resources/ResourceEntities.cs
  • src/backend/Baseera.Infrastructure/Persistence/BaseeraDbContext.cs
  • src/backend/Baseera.Infrastructure/Persistence/Configurations/ResourceConfigurations.cs
  • src/backend/Baseera.Infrastructure/Persistence/DatabaseInitializer.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/20260725111037_PhaseD5ResourceReadinessCore.Designer.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/20260725111037_PhaseD5ResourceReadinessCore.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/BaseeraDbContextModelSnapshot.cs
  • src/backend/tests/Baseera.IntegrationTests/ResourceReadinessIntegrationTests.cs
  • src/backend/tests/Baseera.UnitTests/Resources/ResourceReadinessPolicyTests.cs
  • src/backend/tests/Baseera.UnitTests/Workspaces/WorkspaceFrameworkTests.cs
  • src/frontend/src/App.notifications.test.tsx
  • src/frontend/src/App.tsx
  • src/frontend/src/api/client.ts
  • src/frontend/src/index.css
  • src/frontend/src/pages/notes/ObservationWorkspacePage.test.tsx
  • src/frontend/src/pages/resources/FacilityResourcesPage.test.tsx
  • src/frontend/src/pages/resources/FacilityResourcesPage.tsx
  • src/frontend/src/pages/workspaces/FacilityWorkspacePage.test.tsx
  • src/frontend/src/pages/workspaces/FacilityWorkspacePage.tsx
  • src/frontend/src/workspaces/WorkspaceShell.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/frontend/src/pages/notes/ObservationWorkspacePage.test.tsx
  • src/frontend/src/App.notifications.test.tsx

Comment thread docs/phase-d5-resource-api-contract.md
Comment thread src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
Comment thread src/backend/Baseera.Application/Abstractions/Abstractions.cs Outdated
Comment thread src/backend/Baseera.Application/Resources/ResourceServices.cs
Comment thread src/backend/Baseera.Application/Resources/ResourceServices.cs
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.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/backend/tests/Baseera.UnitTests/Resources/ResourceAccessPolicyTests.cs (1)

10-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the null mapping branch.

ViewPermissionFor has a _ => null fallback that no case exercises. Adding a case for a non-resource enum member (or asserting null for an unmapped value) locks in that new ResourceType members are denied by default rather than silently falling through.

🤖 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.UnitTests/Resources/ResourceAccessPolicyTests.cs`
around lines 10 - 17, Add a theory case to
ResourceAccessPolicyTests.ViewPermissionFor_maps_resource_types using a
non-resource or otherwise unmapped ResourceType value with a null expected
permission. This must exercise the _ => null fallback in
ResourceAccessPolicy.ViewPermissionFor and verify unmapped members are denied by
default.
src/backend/tests/Baseera.IntegrationTests/ResourceReadinessIntegrationTests.cs (2)

420-462: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert the valid creation immediately after issuing it.

create is awaited at Line 420 but only validated at Line 462, after the foreign-unit setup and badCreate assertions. Checking it up front makes a failure of the happy path unambiguous.

🤖 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/ResourceReadinessIntegrationTests.cs`
around lines 420 - 462, Move the create.EnsureSuccessStatusCode() call
immediately after the initial valid PostAsJsonAsync request, before creating the
foreign facility unit and issuing badCreate. Leave the foreign-unit setup and
badCreate status assertion unchanged.

327-335: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Materialize the returned IDs before querying EF Core.

ids.Select(x => x!.Id).Contains(o.Id) embeds an in-memory projection inside the Where predicate. Use a pre-materialized ID array so the repository filter stays stable, translation is deterministic, and null response handling fails clearly.

♻️ Proposed refactor
-        var ids = await Task.WhenAll(responses.Select(r => r.Content.ReadFromJsonAsync<CreateResponse>(JsonOptions)));
+        var created = await Task.WhenAll(responses.Select(r => r.Content.ReadFromJsonAsync<CreateResponse>(JsonOptions)));
+        Assert.All(created, item => Assert.NotNull(item));
+        var ids = created.Select(item => item!.Id).ToArray();
 
         using var verifyScope = factory.Services.CreateScope();
         var verifyDb = verifyScope.ServiceProvider.GetRequiredService<BaseeraDbContext>();
         var numbers = await verifyDb.MaintenanceWorkOrders
-            .Where(o => ids.Select(x => x!.Id).Contains(o.Id))
+            .Where(o => ids.Contains(o.Id))
             .Select(o => o.WorkOrderNumber)
             .ToListAsync();
🤖 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/ResourceReadinessIntegrationTests.cs`
around lines 327 - 335, Materialize the response IDs before the EF Core query in
ResourceReadinessIntegrationTests, validating that each CreateResponse is
non-null so failures are explicit. Use the resulting ID array in the
MaintenanceWorkOrders.Where predicate instead of projecting ids inline, while
preserving the existing distinct work-order number assertion.
🤖 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/Resources/ResourceAccessPolicy.cs`:
- Around line 76-86: Reject unsupported import-batch statuses consistently
across validation and database constraints. In ResourceAccessPolicy.cs, update
the status validation to return false after the explicit Confirmed and Previewed
cases. In
src/backend/Baseera.Infrastructure/Persistence/Migrations/20260725151507_PhaseD5ResourceReadinessCore.cs
lines 122-124, update the migration check constraint to allow only Previewed and
Confirmed, then regenerate the corresponding model metadata in
src/backend/Baseera.Infrastructure/Persistence/Migrations/20260725151507_PhaseD5ResourceReadinessCore.Designer.cs
lines 5120-5124.

In
`@src/backend/tests/Baseera.IntegrationTests/ResourceReadinessIntegrationTests.cs`:
- Around line 140-144: Add an explicit non-empty assertion for the assets
collection in the resource-readiness test before Assert.All, ensuring the
facility resource type filtering is exercised while preserving the existing
communication-device type assertion.

---

Nitpick comments:
In
`@src/backend/tests/Baseera.IntegrationTests/ResourceReadinessIntegrationTests.cs`:
- Around line 420-462: Move the create.EnsureSuccessStatusCode() call
immediately after the initial valid PostAsJsonAsync request, before creating the
foreign facility unit and issuing badCreate. Leave the foreign-unit setup and
badCreate status assertion unchanged.
- Around line 327-335: Materialize the response IDs before the EF Core query in
ResourceReadinessIntegrationTests, validating that each CreateResponse is
non-null so failures are explicit. Use the resulting ID array in the
MaintenanceWorkOrders.Where predicate instead of projecting ids inline, while
preserving the existing distinct work-order number assertion.

In `@src/backend/tests/Baseera.UnitTests/Resources/ResourceAccessPolicyTests.cs`:
- Around line 10-17: Add a theory case to
ResourceAccessPolicyTests.ViewPermissionFor_maps_resource_types using a
non-resource or otherwise unmapped ResourceType value with a null expected
permission. This must exercise the _ => null fallback in
ResourceAccessPolicy.ViewPermissionFor and verify unmapped members are denied by
default.
🪄 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: 5a406325-4b25-4df6-9503-35699d3b11b0

📥 Commits

Reviewing files that changed from the base of the PR and between c51865a and aeb1f2f.

📒 Files selected for processing (26)
  • docs/permissions-matrix.md
  • docs/phase-d5-resource-api-contract.md
  • docs/phase-d5-resource-completion-report.md
  • docs/phase-d5-resource-domain-model.md
  • docs/phase-d5-resource-import-contract.md
  • docs/phase-d5-resource-migration.md
  • docs/phase-d5-resource-permissions.md
  • docs/phase-d5-resource-test-matrix.md
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/backend/Baseera.Application/Abstractions/Abstractions.cs
  • src/backend/Baseera.Application/Resources/ResourceAccessPolicy.cs
  • src/backend/Baseera.Application/Resources/ResourceDtos.cs
  • src/backend/Baseera.Application/Resources/ResourceServices.cs
  • src/backend/Baseera.Application/Workspaces/FacilityWorkspaceReadService.cs
  • src/backend/Baseera.Domain/Resources/ResourceEntities.cs
  • src/backend/Baseera.Infrastructure/Persistence/BaseeraDbContext.cs
  • src/backend/Baseera.Infrastructure/Persistence/Configurations/EntityConfigurations.cs
  • src/backend/Baseera.Infrastructure/Persistence/Configurations/ResourceConfigurations.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/20260725151507_PhaseD5ResourceReadinessCore.Designer.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/20260725151507_PhaseD5ResourceReadinessCore.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/BaseeraDbContextModelSnapshot.cs
  • src/backend/tests/Baseera.IntegrationTests/ResourceReadinessIntegrationTests.cs
  • src/backend/tests/Baseera.UnitTests/Forms/Campaigns/FormCampaignCoreTests.cs
  • src/backend/tests/Baseera.UnitTests/NoteWorkflowTests.cs
  • src/backend/tests/Baseera.UnitTests/Resources/ResourceAccessPolicyTests.cs
  • src/backend/tests/Baseera.UnitTests/Workspaces/WorkspaceFrameworkTests.cs
🚧 Files skipped from review as they are similar to previous changes (14)
  • docs/phase-d5-resource-migration.md
  • docs/phase-d5-resource-import-contract.md
  • docs/phase-d5-resource-permissions.md
  • docs/phase-d5-resource-api-contract.md
  • docs/phase-d5-resource-test-matrix.md
  • docs/phase-d5-resource-completion-report.md
  • src/backend/tests/Baseera.UnitTests/Workspaces/WorkspaceFrameworkTests.cs
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/backend/Baseera.Infrastructure/Persistence/Configurations/ResourceConfigurations.cs
  • src/backend/Baseera.Application/Resources/ResourceDtos.cs
  • src/backend/Baseera.Infrastructure/Persistence/Migrations/BaseeraDbContextModelSnapshot.cs
  • src/backend/Baseera.Domain/Resources/ResourceEntities.cs
  • src/backend/Baseera.Application/Workspaces/FacilityWorkspaceReadService.cs
  • src/backend/Baseera.Application/Resources/ResourceServices.cs

Comment thread src/backend/Baseera.Application/Resources/ResourceAccessPolicy.cs Outdated
Comment thread src/backend/tests/Baseera.IntegrationTests/ResourceReadinessIntegrationTests.cs Outdated
henter36 and others added 2 commits July 25, 2026 18:56
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud

Copy link
Copy Markdown

@henter36
henter36 merged commit 1d2d809 into main Jul 25, 2026
7 checks passed
@henter36
henter36 deleted the phase-d5-facility-resource-readiness-core branch July 25, 2026 17:06
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.

[Workspaces] Integrated Resource Center

1 participant