Skip to content

Conversation

@sainak
Copy link
Member

@sainak sainak commented Jan 16, 2026

Proposed Changes

  • feat: add availability status to FacilityLocation

Merge Checklist

  • Tests added/fixed
  • Update docs in /docs
  • Linting Complete
  • Any other necessary step

Only PR's with test cases included and passing lint and test pipelines will be reviewed

@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins

Summary by CodeRabbit

Release Notes

  • New Features
    • Facility locations now automatically track availability status throughout their operational lifecycle. When a location is assigned to an encounter, its status becomes active. Upon encounter completion or location removal, the status reverts to available, providing real-time visibility into which facilities are currently in use.

✏️ Tip: You can customize this high-level summary in your review settings.

@sainak sainak requested a review from a team as a code owner January 16, 2026 16:26
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

Introduces a new availability_status field to FacilityLocation with automatic status transitions during location lifecycle events: create, update, destroy, and encounter completion. Includes database migration, model updates, API logic, specification changes, and test coverage.

Changes

Cohort / File(s) Summary
Core Schema & Migration
care/emr/models/location.py, care/emr/migrations/0057_facilitylocation_availability_status.py, care/emr/resources/location/spec.py
Added availability_status CharField to FacilityLocation model. New migration adds field with default 'available' and data migration sets status to 'active' for locations with active encounters. Updated LocationEncounterAvailabilityStatusChoices enum to include new "available" member and refined field type in FacilityLocationListSpec.
API Implementation
care/emr/api/viewsets/location.py
Implemented status lifecycle management: on create/update, set availability_status based on instance status; on destroy, set to available under lock; in encounter completion handler, set related location status to available alongside existing updates.
Test Coverage
care/emr/tests/test_location_api.py
Added assertions verifying availability_status transitions to active after encounter creation and to available after deletion or completion. Refactored test logic to use cached location instance for multiple assertions.
Fixture Update
care/emr/management/commands/load_fixtures.py
Updated operational_status from "O" to "U" in FacilityLocationWriteSpec fixture data.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description lacks key details: no explanation of what the feature does, no issue link, no architecture rationale, and minimal context beyond a checklist copy-paste. Add a brief explanation of the availability_status feature, link the associated issue, explain the problem being solved, and provide context for the implementation changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding an availability_status field to FacilityLocation with appropriate logic for state transitions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sainak/reserved-location-status

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@care/emr/api/viewsets/location.py`:
- Around line 360-367: The lock in perform_destroy is acquired after calling
super().perform_destroy, allowing a race where a concurrent create/update can
change location.availability_status and be clobbered; move the
Lock(f"facility_location:{instance.location.id}") context to wrap the deletion
and the subsequent update/reset so that super().perform_destroy(instance),
setting instance.location.availability_status to available,
instance.location.save(update_fields=["availability_status"]), and
self.reset_encounter_location_association(instance.location) all execute while
holding the same lock (referencing perform_destroy, Lock, and
reset_encounter_location_association to locate the code).

In `@care/emr/models/location.py`:
- Around line 12-16: Update the FacilityLocation model to match migration 0057
by adding the missing default for availability_status: modify the
availability_status field on class FacilityLocation (in models.location) to
include default='available' so new instances get the same default as the
migration; ensure any model imports or validations remain unchanged and run
makemigrations/migrate if needed to keep model state in sync with migration
0057.
🧹 Nitpick comments (1)
care/emr/api/viewsets/location.py (1)

337-345: Unify availability mapping so create/update can’t drift.
Right now completed → available is only applied in update. If someone ever creates a completed encounter, you’ll store completed instead of available. A tiny helper keeps this consistent and future-proof.

♻️ Suggested refactor
 class FacilityLocationEncounterViewSet(EMRModelViewSet):
+    def _to_availability_status(self, status):
+        if status == LocationEncounterAvailabilityStatusChoices.completed.value:
+            return LocationEncounterAvailabilityStatusChoices.available.value
+        return status
+
     def perform_create(self, instance):
         location = self.get_location_obj()
         with Lock(f"facility_location:{location.id}"):
             instance.location = location
             self._validate_data(instance)
             super().perform_create(instance)
-            location.availability_status = instance.status
+            location.availability_status = self._to_availability_status(instance.status)
             location.save(update_fields=["availability_status"])
             self.reset_encounter_location_association(location)
 
     def perform_update(self, instance):
         location = self.get_location_obj()
         with Lock(f"facility_location:{location.id}"):
             # Keep in mind that instance here is an ORM instance and not pydantic
             self._validate_data(instance, self.get_object())
             super().perform_update(instance)
-            status = instance.status
-            if status == LocationEncounterAvailabilityStatusChoices.completed.value:
-                status = LocationEncounterAvailabilityStatusChoices.available.value
-            location.availability_status = status
+            location.availability_status = self._to_availability_status(instance.status)
             location.save(update_fields=["availability_status"])
             self.reset_encounter_location_association(location)

Also applies to: 347-357

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db890c7 and 32f8186.

📒 Files selected for processing (6)
  • care/emr/api/viewsets/location.py
  • care/emr/management/commands/load_fixtures.py
  • care/emr/migrations/0057_facilitylocation_availability_status.py
  • care/emr/models/location.py
  • care/emr/resources/location/spec.py
  • care/emr/tests/test_location_api.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (.cursorrules)

**/*.py: Prioritize readability and maintainability; follow Django's coding style guide (PEP 8 compliance).
Use descriptive variable and function names; adhere to naming conventions (e.g., lowercase with underscores for functions and variables).

Files:

  • care/emr/api/viewsets/location.py
  • care/emr/models/location.py
  • care/emr/tests/test_location_api.py
  • care/emr/resources/location/spec.py
  • care/emr/management/commands/load_fixtures.py
  • care/emr/migrations/0057_facilitylocation_availability_status.py
**/{models,views,management/commands}/*.py

📄 CodeRabbit inference engine (.cursorrules)

Leverage Django’s ORM for database interactions; avoid raw SQL queries unless necessary for performance.

Files:

  • care/emr/models/location.py
  • care/emr/management/commands/load_fixtures.py
**/tests/**/*.py

📄 CodeRabbit inference engine (.cursorrules)

Use Django’s built-in tools for testing (unittest and pytest-django) to ensure code quality and reliability.

Files:

  • care/emr/tests/test_location_api.py
🧠 Learnings (2)
📚 Learning: 2025-07-08T13:27:05.363Z
Learnt from: NikhilA8606
Repo: ohcnetwork/care PR: 3124
File: care/emr/resources/medication/administration/spec.py:163-164
Timestamp: 2025-07-08T13:27:05.363Z
Learning: In the care codebase, all EMR models inherit from EMRBaseModel which inherits from BaseModel (in care/utils/models/base.py). BaseModel provides common fields including created_date and modified_date with auto_now_add=True and auto_now=True respectively. These fields are available on all EMR models through inheritance.

Applied to files:

  • care/emr/models/location.py
📚 Learning: 2024-11-28T06:16:31.373Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2620
File: care/facility/models/facility.py:306-311
Timestamp: 2024-11-28T06:16:31.373Z
Learning: In the 'care' project, moving imports of models like `Asset`, `AssetLocation`, `FacilityDefaultAssetLocation`, and `PatientSample` to the module level in `care/facility/models/facility.py` causes circular imports. Therefore, it's acceptable to keep these imports inside the `delete` method of the `Facility` class.

Applied to files:

  • care/emr/tests/test_location_api.py
🧬 Code graph analysis (3)
care/emr/api/viewsets/location.py (3)
care/emr/resources/location/spec.py (1)
  • LocationEncounterAvailabilityStatusChoices (13-18)
care/emr/api/viewsets/base.py (1)
  • perform_destroy (250-252)
care/utils/lock.py (1)
  • Lock (12-30)
care/emr/tests/test_location_api.py (1)
care/emr/models/location.py (1)
  • FacilityLocation (12-128)
care/emr/migrations/0057_facilitylocation_availability_status.py (2)
care/emr/models/location.py (1)
  • FacilityLocation (12-128)
care/emr/api/viewsets/location.py (1)
  • filter (40-45)
🪛 Ruff (0.14.11)
care/emr/migrations/0057_facilitylocation_availability_status.py

6-6: Unused function argument: schema_editor

(ARG001)


13-15: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


17-25: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test / Test
🔇 Additional comments (12)
care/emr/management/commands/load_fixtures.py (1)

623-632: Operational status update looks fine.
Assuming "U" is the intended default operational status, this change is consistent and harmless.

care/emr/resources/location/spec.py (2)

13-18: Enum extension is clean and consistent.
Adding available fits the lifecycle states and keeps the enum cohesive.


117-122: Typed availability_status is a good tighten-up.
The list spec now reflects the real domain values instead of a plain string.

care/emr/migrations/0057_facilitylocation_availability_status.py (1)

6-25: Migration logic is straightforward and sane.
Backfilling active for rows with current_encounter is the right move.

care/emr/api/viewsets/location.py (1)

480-486: Close flow update is solid.
Setting availability_status to available alongside clearing current_encounter is exactly what you want here.

care/emr/tests/test_location_api.py (7)

773-777: Good, we actually verify the location flips to active now.
This assertion pins the new availability_status behavior after create.


794-799: Nice to see the state asserted before the conflict path.
Covers both current_encounter and availability_status.


829-833: Yep, this makes the overlap test actually prove the location is active.


869-873: Good—precondition is now explicit instead of implied.


1046-1050: Solid post-delete verification for availability_status.


1108-1112: Good coverage of the completed → available transition.


1189-1194: Finally checking both current_encounter and availability_status—nice.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 360 to +367
def perform_destroy(self, instance):
super().perform_destroy(instance)
self.reset_encounter_location_association(instance.location)
with Lock(f"facility_location:{instance.location.id}"):
instance.location.availability_status = (
LocationEncounterAvailabilityStatusChoices.available.value
)
instance.location.save(update_fields=["availability_status"])
self.reset_encounter_location_association(instance.location)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Lock acquisition happens too late in destroy.
Because the lock is taken after deletion, a concurrent create/update can set availability_status and then get overwritten by this destroy path. Wrap the delete + update in the same lock.

🔒 Proposed fix
     def perform_destroy(self, instance):
-        super().perform_destroy(instance)
-        with Lock(f"facility_location:{instance.location.id}"):
+        with Lock(f"facility_location:{instance.location.id}"):
+            super().perform_destroy(instance)
             instance.location.availability_status = (
                 LocationEncounterAvailabilityStatusChoices.available.value
             )
             instance.location.save(update_fields=["availability_status"])
             self.reset_encounter_location_association(instance.location)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def perform_destroy(self, instance):
super().perform_destroy(instance)
self.reset_encounter_location_association(instance.location)
with Lock(f"facility_location:{instance.location.id}"):
instance.location.availability_status = (
LocationEncounterAvailabilityStatusChoices.available.value
)
instance.location.save(update_fields=["availability_status"])
self.reset_encounter_location_association(instance.location)
def perform_destroy(self, instance):
with Lock(f"facility_location:{instance.location.id}"):
super().perform_destroy(instance)
instance.location.availability_status = (
LocationEncounterAvailabilityStatusChoices.available.value
)
instance.location.save(update_fields=["availability_status"])
self.reset_encounter_location_association(instance.location)
🤖 Prompt for AI Agents
In `@care/emr/api/viewsets/location.py` around lines 360 - 367, The lock in
perform_destroy is acquired after calling super().perform_destroy, allowing a
race where a concurrent create/update can change location.availability_status
and be clobbered; move the Lock(f"facility_location:{instance.location.id}")
context to wrap the deletion and the subsequent update/reset so that
super().perform_destroy(instance), setting instance.location.availability_status
to available, instance.location.save(update_fields=["availability_status"]), and
self.reset_encounter_location_association(instance.location) all execute while
holding the same lock (referencing perform_destroy, Lock, and
reset_encounter_location_association to locate the code).

Comment on lines 12 to 16
class FacilityLocation(EMRBaseModel):
status = models.CharField(max_length=255)
operational_status = models.CharField(max_length=255)
availability_status = models.CharField(max_length=255)
name = models.CharField(max_length=255)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the location.py file
fd -t f "location.py" --path "*emr/models*"

Repository: ohcnetwork/care

Length of output: 292


🏁 Script executed:

# Check the actual model definition
cat -n care/emr/models/location.py | head -30

Repository: ohcnetwork/care

Length of output: 1560


🏁 Script executed:

# Find migration files related to this change
fd -t f "*.py" --path "*migrations*" | grep -E "(location|facility)" | head -20

Repository: ohcnetwork/care

Length of output: 292


🏁 Script executed:

# Find migration files - corrected syntax
fd -t f "\.py$" care/emr/migrations | head -30

Repository: ohcnetwork/care

Length of output: 1825


🏁 Script executed:

# Search for migrations related to FacilityLocation
rg "availability_status" care/emr/migrations/ -A 3 -B 3

Repository: ohcnetwork/care

Length of output: 5469


🏁 Script executed:

# Check if there are any recent changes to this file
git log --oneline care/emr/models/location.py | head -10

Repository: ohcnetwork/care

Length of output: 117


🏁 Script executed:

# Look at the full model to understand the context better
cat -n care/emr/models/location.py | tail -20

Repository: ohcnetwork/care

Length of output: 889


Add default value to match migration 0057.
The availability_status field definition is missing the default='available' that migration 0057 specifies. Without it, object creation will fail unless the field is explicitly set—fun times debugging that later.

🩹 Proposed fix
-    availability_status = models.CharField(max_length=255)
+    availability_status = models.CharField(max_length=255, default="available")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class FacilityLocation(EMRBaseModel):
status = models.CharField(max_length=255)
operational_status = models.CharField(max_length=255)
availability_status = models.CharField(max_length=255)
name = models.CharField(max_length=255)
class FacilityLocation(EMRBaseModel):
status = models.CharField(max_length=255)
operational_status = models.CharField(max_length=255)
availability_status = models.CharField(max_length=255, default="available")
name = models.CharField(max_length=255)
🤖 Prompt for AI Agents
In `@care/emr/models/location.py` around lines 12 - 16, Update the
FacilityLocation model to match migration 0057 by adding the missing default for
availability_status: modify the availability_status field on class
FacilityLocation (in models.location) to include default='available' so new
instances get the same default as the migration; ensure any model imports or
validations remain unchanged and run makemigrations/migrate if needed to keep
model state in sync with migration 0057.

@codecov
Copy link

codecov bot commented Jan 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.84%. Comparing base (db890c7) to head (32f8186).

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3469      +/-   ##
===========================================
+ Coverage    74.82%   74.84%   +0.01%     
===========================================
  Files          465      465              
  Lines        21115    21127      +12     
  Branches      2183     2184       +1     
===========================================
+ Hits         15800    15812      +12     
  Misses        4835     4835              
  Partials       480      480              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines 13 to 18
class LocationEncounterAvailabilityStatusChoices(str, Enum):
available = "available"
planned = "planned"
active = "active"
reserved = "reserved"
active = "active"
completed = "completed"
Copy link
Member

Choose a reason for hiding this comment

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

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.

3 participants