---
phase: 03-manage
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - possession/models.py
  - tests/test_models.py
autonomous: true
requirements:
  - ITEM-01
  - ITEM-02
  - ITEM-03

must_haves:
  truths:
    - "update_item() updates any subset of item fields without touching unspecified fields"
    - "delete_item() removes the item row permanently"
    - "Moving an item (changing room_id/container_id) works via update_item()"
  artifacts:
    - path: "possession/models.py"
      provides: "update_item and delete_item CRUD functions"
      contains: "def update_item"
    - path: "tests/test_models.py"
      provides: "Pytest coverage for update_item and delete_item"
      contains: "test_update_item"
  key_links:
    - from: "possession/tui/screens/main.py"
      to: "possession/models.py"
      via: "from possession.models import update_item, delete_item"
      pattern: "update_item|delete_item"
---

<objective>
Add update_item and delete_item to the data model layer.

Purpose: Phase 3 TUI plans (edit form, delete confirmation) need these two model functions. Both are missing from models.py. This plan delivers them with tests before TUI work begins.
Output: Two new functions in models.py — update_item (supports partial field updates, enabling ITEM-01 edit and ITEM-03 move) and delete_item (enabling ITEM-02 delete). Covered by pytest.
</objective>

<execution_context>
@/Users/paritoshpatil/.claude/get-shit-done/workflows/execute-plan.md
@/Users/paritoshpatil/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/ROADMAP.md
@possession/models.py
@tests/test_models.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add update_item and delete_item to models.py</name>
  <files>possession/models.py</files>
  <action>
Add two new functions after the existing create_item and list_items functions in possession/models.py.

**update_item signature:**
```python
def update_item(
    db_path: Path,
    item_id: int,
    name: Optional[str] = None,
    description: Optional[str] = None,
    room_id: Optional[int] = None,
    container_id: Optional[int] = None,
    category_id: Optional[int] = None,
    purchase_date: Optional[str] = None,
    cost: Optional[float] = None,
) -> None:
```

Implementation: Build a dynamic UPDATE statement from only the fields that are not None (sentinel pattern — caller passes None to mean "don't change this field"). Use a list of (column, value) pairs to build SET clause. Include item_id in WHERE. Raise ValueError if rowcount == 0 (item not found). Follow the existing per-call connection pattern (get_connection / conn.commit() / conn.close() in finally).

Important: room_id, container_id, category_id can legitimately be set TO None/NULL (to clear a location). Use a sentinel `_UNSET = object()` default instead of None for those three fields — or use a kwargs dict approach. Simpler: Use `_UNSET = object()` sentinel at module level and default each kwarg to `_UNSET`. In the function body, only include a field in the UPDATE if `value is not _UNSET`. This correctly handles "caller wants to set field to NULL" vs "caller doesn't want to change field."

**delete_item signature:**
```python
def delete_item(db_path: Path, item_id: int) -> None:
```

Implementation: DELETE FROM items WHERE id=?. Raise ValueError if rowcount == 0 (item not found). Per-call connection pattern.

Also add both to the imports section if needed (they use existing imports: Path, Optional, get_connection, sqlite3).
  </action>
  <verify>
    <automated>cd /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2 && python3 -c "from possession.models import update_item, delete_item; print('OK')"</automated>
  </verify>
  <done>update_item and delete_item importable from possession.models without error</done>
</task>

<task type="auto">
  <name>Task 2: Add pytest coverage for update_item and delete_item</name>
  <files>tests/test_models.py</files>
  <action>
Add test cases to the existing test_models.py file covering update_item and delete_item behavior. Do not remove existing tests.

Tests to add:

**For update_item:**
- test_update_item_name: create item, update name, list_items and assert new name appears
- test_update_item_partial: create item with name only, update description only, verify name unchanged and description updated
- test_update_item_clear_room: create item with room_id set, update with room_id explicitly cleared (set to None), verify room_id is NULL in DB (use list_items and check room_name is None/empty)
- test_update_item_not_found: call update_item with nonexistent item_id, assert ValueError raised

**For delete_item:**
- test_delete_item: create item, delete_item, list_items returns empty
- test_delete_item_not_found: call delete_item with nonexistent item_id, assert ValueError raised

Use the existing tmp_db fixture (or equivalent pattern used in the file). Follow the same import style as existing tests. Use Python 3.9 compatible type annotations (Optional[X] not X | None).
  </action>
  <verify>
    <automated>cd /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2 && python3 -m pytest tests/test_models.py -x -q 2>&1 | tail -5</automated>
  </verify>
  <done>All tests in test_models.py pass including new update_item and delete_item tests</done>
</task>

</tasks>

<verification>
Run full test suite: `cd /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2 && python3 -m pytest tests/ -x -q`
All tests pass. update_item and delete_item importable. Sentinel pattern correctly handles NULL-clearing of optional FK fields.
</verification>

<success_criteria>
- update_item and delete_item exist in possession/models.py
- update_item uses sentinel to distinguish "don't change" from "set to NULL"
- delete_item raises ValueError for missing item_id
- All existing tests still pass
- 6 new test cases all green
</success_criteria>

<output>
After completion, create `.planning/phases/03-manage/03-01-SUMMARY.md`
</output>
