Skip to content

[Eng-441] feat:ability to delete resource category#3703

Open
nandkishorr wants to merge 7 commits into
developfrom
ENG-441-ability-to-delete-resource-category
Open

[Eng-441] feat:ability to delete resource category#3703
nandkishorr wants to merge 7 commits into
developfrom
ENG-441-ability-to-delete-resource-category

Conversation

@nandkishorr

@nandkishorr nandkishorr commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Proposed Changes

  • Added DeleteMixin for resource category
  • All resource category fk's to PROTECT Mode
  • Added relative testcases

Associated Issue

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

  • New Features
    • Resource categories now use safer deletion: deletion is rejected if the category has child categories or is referenced by existing records (and the category is soft-deleted when allowed).
  • Bug Fixes
    • Updated deletion checks and permissions for resource-category removal.
    • Parent category metadata is now automatically updated after a successful deletion.
    • Related model references are now protected from hard deletion.
  • Tests
    • Expanded coverage for listing with empty parent filters and for delete scenarios across permissions and reference/child constraints.

@nandkishorr nandkishorr self-assigned this Jul 6, 2026
@nandkishorr nandkishorr requested a review from a team as a code owner July 6, 2026 08:55
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds soft-delete protection to ResourceCategoryViewSet, with destroy validation against child categories and linked EMR records. Related category foreign keys now use PROTECT, with a matching migration and expanded API coverage. Fancy, a deletion guard needed tests.

Changes

ResourceCategory delete protection

Layer / File(s) Summary
FK on_delete change and migration
care/emr/models/activity_definition.py, care/emr/models/charge_item_definition.py, care/emr/models/product_knowledge.py, care/emr/migrations/0080_alter_activitydefinition_category_and_more.py
category foreign keys on three models switch from CASCADE to PROTECT, with a migration applying the field changes.
ViewSet destroy guard and authorization
care/emr/api/viewsets/resource_category.py
ResourceCategoryViewSet gains EMRDestroyMixin, adds destroy validation for children and linked records, soft-deletes within a transaction, updates the parent has_children flag, and delegates destroy authorization to authorize_update.
Delete and list filter test coverage
care/emr/tests/test_resource_category_api.py
New tests cover super user/regular user deletion permissions, blocked deletion when associated with ProductKnowledge, ActivityDefinition, ChargeItemDefinition, or child categories, plus a list test for an empty parent filter.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • ohcnetwork/care#3619: Updates authorize_update/permission-denied messaging for resource categories, which is directly relevant to the new authorize_destroy path.

Suggested labels: waiting-for-review, Tests

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly states the main change: adding delete capability for resource categories.
Description check ✅ Passed The description covers proposed changes, associated issue, and the merge checklist in the required template.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 ENG-441-ability-to-delete-resource-category

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.

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds soft-delete support for ResourceCategory via EMRDestroyMixin, guards deletion with checks for active children and associated resources, and upgrades the three FK references from CASCADE to PROTECT to prevent accidental DB-level cascades.

  • validate_destroy blocks deletion when the category has non-deleted children or active ActivityDefinition/ChargeItemDefinition/ProductKnowledge records; perform_destroy soft-deletes within a transaction and correctly refreshes the parent's has_children flag afterward.
  • The migration changes all three external-model category FKs to PROTECT, and the test suite covers permission variants and all three associated-resource blocking scenarios.

Confidence Score: 5/5

Safe to merge — the deletion logic is correctly guarded, soft-delete is wrapped in a transaction, and the parent has_children flag is properly refreshed using the filtered default manager.

The core deletion path is well-implemented: validate_destroy uses the app's soft-delete-aware ORM manager so only active children and resources block deletion, perform_destroy wraps both the soft-delete and the parent flag update in a single atomic transaction, and authorize_destroy correctly delegates to the existing write-permission check. The PROTECT migration covers all three models that reference ResourceCategory. No data-loss or incorrect-state scenarios were identified in the changed code.

care/emr/api/viewsets/resource_category.py — EMRDestroyMixin placement after EMRBaseViewSet (flagged in a previous review cycle)

Important Files Changed

Filename Overview
care/emr/api/viewsets/resource_category.py Adds EMRDestroyMixin with validate_destroy, perform_destroy, and authorize_destroy; EMRDestroyMixin is still placed after EMRBaseViewSet (MRO inconsistency flagged in previous review)
care/emr/migrations/0080_alter_activitydefinition_category_and_more.py Alters category FK on ActivityDefinition, ChargeItemDefinition, and ProductKnowledge from CASCADE to PROTECT; correct and complete for all models referencing ResourceCategory
care/emr/tests/test_resource_category_api.py Adds comprehensive delete test coverage: permission checks, children block, and associated resource blocks for all three resource types; also adds a list filter edge case test
care/emr/models/activity_definition.py Changes category FK on_delete from CASCADE to PROTECT to prevent accidental deletion of referenced categories
care/emr/models/charge_item_definition.py Changes category FK on_delete from CASCADE to PROTECT; consistent with other model changes in this PR
care/emr/models/product_knowledge.py Changes category FK on_delete from CASCADE to PROTECT; consistent with other model changes in this PR

Reviews (3): Last reviewed commit: "Merge branch 'ENG-441-ability-to-delete-..." | Re-trigger Greptile

Comment thread care/emr/api/viewsets/resource_category.py Outdated
Comment thread care/emr/api/viewsets/resource_category.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
care/emr/tests/test_resource_category_api.py (1)

271-292: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test name doesn't match its assertion.

The test is named test_list_resource_categories_with_parent_filter_no_results but it asserts exactly 1 result (the root category). A more accurate name would reflect that an empty ?parent= filters to root-level categories only.

-    def test_list_resource_categories_with_parent_filter_no_results(self):
+    def test_list_resource_categories_with_empty_parent_filter_returns_root_categories(self):
🤖 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 `@care/emr/tests/test_resource_category_api.py` around lines 271 - 292, The
test name in test_list_resource_categories_with_parent_filter_no_results does
not match what it asserts, since it expects the root category to be returned
rather than no results. Rename this test to reflect that an empty parent filter
returns root-level resource categories, and keep the assertions aligned with
that behavior in test_list_resource_categories_with_parent_filter_no_results and
the related get_url query usage.
care/emr/api/viewsets/resource_category.py (1)

115-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move validation logic into validate_destroy for consistency.

This class already separates validation (validate_data) from side effects (perform_create/perform_update). The destroy path has an analogous validate_destroy hook (default no-op in EMRDestroyMixin) that isn't used — all checks are bundled into perform_destroy instead, which departs from that pattern.

♻️ Suggested restructuring
-    def perform_destroy(self, instance):
-        if instance.children.exists():
-            raise ValidationError("Cannot delete a resource category with children")
-
-        resource_type = instance.resource_type
-        if (
-            (
-                resource_type
-                == ResourceCategoryResourceTypeOptions.activity_definition.value
-                and ActivityDefinition.objects.filter(category=instance).exists()
-            )
-            or (
-                resource_type
-                == ResourceCategoryResourceTypeOptions.charge_item_definition.value
-                and ChargeItemDefinition.objects.filter(category=instance).exists()
-            )
-            or (
-                resource_type
-                == ResourceCategoryResourceTypeOptions.product_knowledge.value
-                and ProductKnowledge.objects.filter(category=instance).exists()
-            )
-        ):
-            raise ValidationError(
-                "Cannot delete a resource category associated with a resource"
-            )
-        super().perform_destroy(instance)
+    def validate_destroy(self, instance):
+        if instance.children.exists():
+            raise ValidationError("Cannot delete a resource category with children")
+
+        resource_model_map = {
+            ResourceCategoryResourceTypeOptions.activity_definition.value: ActivityDefinition,
+            ResourceCategoryResourceTypeOptions.charge_item_definition.value: ChargeItemDefinition,
+            ResourceCategoryResourceTypeOptions.product_knowledge.value: ProductKnowledge,
+        }
+        model = resource_model_map.get(instance.resource_type)
+        if model and model.objects.filter(category=instance).exists():
+            raise ValidationError(
+                "Cannot delete a resource category associated with a resource"
+            )
🤖 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 `@care/emr/api/viewsets/resource_category.py` around lines 115 - 140, Move the
destroy-time checks out of perform_destroy and into validate_destroy for
consistency with validate_data and the create/update flow. Keep the existing
child/resource association checks in ResourceCategory’s destroy path, but have
validate_destroy raise the ValidationError(s) before any deletion happens, and
let perform_destroy only call super().perform_destroy(instance). Use the
existing validate_destroy hook from EMRDestroyMixin and the
ResourceCategoryResourceTypeOptions / ActivityDefinition / ChargeItemDefinition
/ ProductKnowledge checks to locate the 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.

Nitpick comments:
In `@care/emr/api/viewsets/resource_category.py`:
- Around line 115-140: Move the destroy-time checks out of perform_destroy and
into validate_destroy for consistency with validate_data and the create/update
flow. Keep the existing child/resource association checks in ResourceCategory’s
destroy path, but have validate_destroy raise the ValidationError(s) before any
deletion happens, and let perform_destroy only call
super().perform_destroy(instance). Use the existing validate_destroy hook from
EMRDestroyMixin and the ResourceCategoryResourceTypeOptions / ActivityDefinition
/ ChargeItemDefinition / ProductKnowledge checks to locate the logic.

In `@care/emr/tests/test_resource_category_api.py`:
- Around line 271-292: The test name in
test_list_resource_categories_with_parent_filter_no_results does not match what
it asserts, since it expects the root category to be returned rather than no
results. Rename this test to reflect that an empty parent filter returns
root-level resource categories, and keep the assertions aligned with that
behavior in test_list_resource_categories_with_parent_filter_no_results and the
related get_url query usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3ca07cd0-3d69-4e59-aa66-c068867fcd73

📥 Commits

Reviewing files that changed from the base of the PR and between c6ca8ef and a0c975a.

📒 Files selected for processing (6)
  • care/emr/api/viewsets/resource_category.py
  • care/emr/migrations/0080_alter_activitydefinition_category_and_more.py
  • care/emr/models/activity_definition.py
  • care/emr/models/charge_item_definition.py
  • care/emr/models/product_knowledge.py
  • care/emr/tests/test_resource_category_api.py

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.81818% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.67%. Comparing base (c6ca8ef) to head (68514e7).

Files with missing lines Patch % Lines
care/emr/api/viewsets/resource_category.py 81.81% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3703      +/-   ##
===========================================
+ Coverage    79.66%   79.67%   +0.01%     
===========================================
  Files          479      479              
  Lines        23010    23032      +22     
  Branches      2379     2382       +3     
===========================================
+ Hits         18330    18350      +20     
- Misses        4080     4082       +2     
  Partials       600      600              

☔ View full report in Codecov by Harness.
📢 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
care/emr/api/viewsets/resource_category.py (2)

116-139: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Authorize before destroy validation. EMRDestroyMixin.destroy() runs validate_destroy() before authorize_destroy(), so a user with list access but no write access can learn whether a category has children or linked resources from the 400 response. Swap the order; the denial path doesn’t need extra gossip.

🤖 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 `@care/emr/api/viewsets/resource_category.py` around lines 116 - 139, In
EMRDestroyMixin.destroy, the current call order reveals validation details
before permission checks; update the destroy flow so authorize_destroy runs
before validate_destroy, preventing users without write access from learning
whether a ResourceCategory has children or linked resources. Use the existing
destroy/authorize_destroy/validate_destroy path in the ResourceCategory viewset
to swap the checks without changing the validation logic itself.

116-154: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Serialize the destroy path in care/emr/api/viewsets/resource_category.py:116-154

  • validate_destroy() and perform_destroy() are split, so a child category or linked ActivityDefinition/ChargeItemDefinition/ProductKnowledge can be created after the exists() checks pass and before deleted=True commits. Since this is a soft-delete, the PROTECT FKs never get a chance to stop that late write.
  • parent.has_children is recomputed without locking parent, so concurrent sibling deletes can leave the flag stale.
🤖 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 `@care/emr/api/viewsets/resource_category.py` around lines 116 - 154, The
destroy flow in validate_destroy and perform_destroy is vulnerable to race
conditions, so move the existence checks and the soft-delete update into one
serialized transaction. Use locking on the ResourceCategory row (and parent when
updating has_children) so child categories or linked ActivityDefinition,
ChargeItemDefinition, or ProductKnowledge records cannot be created between the
checks and the deleted=True save. Also lock the parent before recomputing
parent.has_children in perform_destroy to keep the flag consistent under
concurrent deletes.
🤖 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 `@care/emr/api/viewsets/resource_category.py`:
- Around line 116-139: In EMRDestroyMixin.destroy, the current call order
reveals validation details before permission checks; update the destroy flow so
authorize_destroy runs before validate_destroy, preventing users without write
access from learning whether a ResourceCategory has children or linked
resources. Use the existing destroy/authorize_destroy/validate_destroy path in
the ResourceCategory viewset to swap the checks without changing the validation
logic itself.
- Around line 116-154: The destroy flow in validate_destroy and perform_destroy
is vulnerable to race conditions, so move the existence checks and the
soft-delete update into one serialized transaction. Use locking on the
ResourceCategory row (and parent when updating has_children) so child categories
or linked ActivityDefinition, ChargeItemDefinition, or ProductKnowledge records
cannot be created between the checks and the deleted=True save. Also lock the
parent before recomputing parent.has_children in perform_destroy to keep the
flag consistent under concurrent deletes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bfb4fd41-1d8f-472e-837f-364bed2bba99

📥 Commits

Reviewing files that changed from the base of the PR and between a0c975a and 68514e7.

📒 Files selected for processing (1)
  • care/emr/api/viewsets/resource_category.py

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