Skip to content

Conversation

@d-p35
Copy link

@d-p35 d-p35 commented Dec 1, 2025

Summary

Implements automatic synchronization between ItemHistory events and CollectionItem location fields. When location-changing history events are created (INITIAL, ARRIVED, VERIFIED, LOCATION_CORRECTION), the item's current_location and is_on_floor fields are automatically updated via Django signals. Also includes a management command for rebuilding all item locations from history.

Related Issues

Changes

Auto-sync Logic:

  • Added Django post_save signal on ItemHistory model to automatically trigger location updates
  • Signal updates current_location and is_on_floor fields when location-changing events are created (INITIAL, ARRIVED, VERIFIED, LOCATION_CORRECTION)

Maintenance Command:

  • Created rebuild_item_locations Django management command for data recovery and consistency checks
  • Command supports --item-id flag to rebuild single items or processes all items when no flag provided

Testing & Code Quality:

  • Added 6 comprehensive test cases covering signal behavior, location updates, and management command

How to Test

cd backend

# Run all inventory tests
python manage.py test inventory.tests

# Test the management command
python manage.py rebuild_item_locations --item-id 1
python manage.py rebuild_item_locations

@d-p35 d-p35 marked this pull request as ready for review December 1, 2025 22:45
Copilot AI review requested due to automatic review settings December 1, 2025 22:45

This comment was marked as outdated.

Copy link
Contributor

@vivjd vivjd left a comment

Choose a reason for hiding this comment

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

Looks good overall! Please review copilot's comments and re-request review!

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings January 6, 2026 23:45

This comment was marked as outdated.

@d-p35
Copy link
Author

d-p35 commented Jan 7, 2026

Instead of creating another copy of LOCATION_CHANGING_EVENTS (already used in utils.py), I extracted it in a separate file called constants.py and imported it for the usages. I couldn't directly import it from utils.py because it would create a circular dependency.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +163 to +168
try:
item: CollectionItem = instance.item
item.update_location_from_history()
except Exception as e:
# Log error but don't raise to prevent disrupting the original save
logger.error(f"Failed to update item location for item {instance.item_id}: {e}")
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The bare except catching all Exception types could hide critical bugs or system errors. Consider either catching more specific exceptions (e.g., DatabaseError, ValidationError) or re-raising certain critical exceptions after logging. This ensures that genuine system errors don't get silently swallowed.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +42

for item in items:
try:
item.update_location_from_history()
updated_count += 1
except Exception as e:
self.stdout.write(self.style.ERROR(f"Error updating item {item.id}: {e}"))

self.stdout.write(self.style.SUCCESS(f"Updated {updated_count} items"))
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The command tracks updated_count for successful updates but doesn't track or report the number of failures. Consider adding a failed_count variable and including it in the final summary message to give users complete visibility into the rebuild operation results.

Suggested change
for item in items:
try:
item.update_location_from_history()
updated_count += 1
except Exception as e:
self.stdout.write(self.style.ERROR(f"Error updating item {item.id}: {e}"))
self.stdout.write(self.style.SUCCESS(f"Updated {updated_count} items"))
failed_count = 0
for item in items:
try:
item.update_location_from_history()
updated_count += 1
except Exception as e:
failed_count += 1
self.stdout.write(self.style.ERROR(f"Error updating item {item.id}: {e}"))
self.stdout.write(
self.style.SUCCESS(
f"Updated {updated_count} items, {failed_count} failures"
)
)

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +89
def test_command_all_items(self):
"""Test command rebuilds all items."""
out = StringIO()
call_command("rebuild_item_locations", stdout=out)

output = out.getvalue()
self.assertIn("Updated", output)
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The test only verifies that the success message is printed but doesn't verify that the item's location fields were actually updated correctly. Consider adding assertions to check that current_location and is_on_floor were updated based on the item's history.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +73
class ItemLocationTest(TestCase):
"""Test the item location functionality."""

def setUp(self):
self.user = User.objects.create_user(email="test@example.com", name="Test User", password="testpass", role="VOLUNTEER")
self.location_storage = Location.objects.create(name="Storage A", location_type="STORAGE")
self.location_floor = Location.objects.create(name="Main Floor", location_type="FLOOR")
self.item = CollectionItem.objects.create(
item_code="TEST001", title="Test Item", current_location=self.location_storage
)

def test_update_location_from_history_initial_event(self):
"""Test location update with INITIAL event."""
# Create INITIAL history event
ItemHistory.objects.create(item=self.item, event_type="INITIAL", to_location=self.location_storage, acted_by=self.user)

# Update location using model method
self.item.update_location_from_history()

self.assertEqual(self.item.current_location, self.location_storage)
self.assertFalse(self.item.is_on_floor)

def test_update_location_from_history_floor_move(self):
"""Test location update when item moves to floor."""
# Create events
ItemHistory.objects.create(item=self.item, event_type="INITIAL", to_location=self.location_storage)
ItemHistory.objects.create(
item=self.item, event_type="ARRIVED", from_location=self.location_storage, to_location=self.location_floor
)

self.item.update_location_from_history()

self.assertEqual(self.item.current_location, self.location_floor)
self.assertTrue(self.item.is_on_floor)

def test_signal_triggers_on_location_changing_event(self):
"""Test that signal updates item location for location-changing events."""
# Create ARRIVED event - should trigger signal
ItemHistory.objects.create(
item=self.item, event_type="ARRIVED", to_location=self.location_floor, from_location=self.location_storage
)

# Refresh item from database
self.item.refresh_from_db()

# Verify location was updated by signal
self.assertEqual(self.item.current_location, self.location_floor)
self.assertTrue(self.item.is_on_floor)

def test_signal_does_not_update_location_for_workflow_events(self):
"""Test that signal triggers but doesn't update location for workflow-only events."""
original_location = self.item.current_location
original_is_on_floor = self.item.is_on_floor

# Create MOVE_REQUESTED event - should NOT trigger signal
ItemHistory.objects.create(
item=self.item, event_type="MOVE_REQUESTED", to_location=self.location_floor, from_location=self.location_storage
)

# Refresh item from database
self.item.refresh_from_db()

# Verify location was NOT updated
self.assertEqual(self.item.current_location, original_location)
self.assertEqual(self.item.is_on_floor, original_is_on_floor)
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

Test coverage is missing for VERIFIED and LOCATION_CORRECTION event types, which are defined as location-changing events in LOCATION_CHANGING_EVENTS. Consider adding test cases to verify that these event types also trigger the signal and correctly update the item's location.

Copilot uses AI. Check for mistakes.
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.

Implement Item History Service to Sync 'current_location'

3 participants