From 6f98618b518ae68035464a0b4711f4a2828e3a30 Mon Sep 17 00:00:00 2001 From: archeious Date: Fri, 17 Apr 2026 18:45:12 -0600 Subject: [PATCH] feat(service): add update_entry for template rows (#21) --- src/quartermaster/service.py | 24 +++++++++++ tests/test_service.py | 80 ++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/quartermaster/service.py b/src/quartermaster/service.py index 5a9cc95..5eff831 100644 --- a/src/quartermaster/service.py +++ b/src/quartermaster/service.py @@ -15,6 +15,8 @@ from quartermaster.groups import ( ) from quartermaster.models import SECTION_LABELS, DebtTarget, Entry, Section +_NOTES_SENTINEL = object() + def zero_tone(value: Decimal) -> str: if value == 0: @@ -125,6 +127,28 @@ def set_entry_notes( return entry +def update_entry( + db: Session, + entry_id: int, + *, + name: str | None = None, + amount: Decimal | None = None, + notes: str | None | object = _NOTES_SENTINEL, +) -> Entry | None: + entry = db.get(Entry, entry_id) + if entry is None: + return None + if name is not None: + entry.name = name.strip() + if amount is not None: + entry.amount = amount + if notes is not _NOTES_SENTINEL: + entry.notes = _clean_notes(notes) # type: ignore[arg-type] + db.commit() + db.refresh(entry) + return entry + + def delete_entry(db: Session, entry_id: int) -> Entry | None: entry = db.get(Entry, entry_id) if entry is None: diff --git a/tests/test_service.py b/tests/test_service.py index c03ede5..90b7a67 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -41,3 +41,83 @@ def test_debt_target_cleared_on_delete(db): service.delete_entry(db, dm.id) target = service.get_debt_target(db) assert target.debt_minimum_id is None + + +def test_update_entry_name_only(db): + entry = service.add_entry(db, Section.subscription, "Twitch", Decimal("10.99")) + updated = service.update_entry(db, entry.id, name="Twitch Prime") + assert updated is not None + assert updated.name == "Twitch Prime" + assert updated.amount == Decimal("10.99") + + +def test_update_entry_amount_only(db): + entry = service.add_entry(db, Section.subscription, "Twitch", Decimal("10.99")) + updated = service.update_entry(db, entry.id, amount=Decimal("11.99")) + assert updated is not None + assert updated.name == "Twitch" + assert updated.amount == Decimal("11.99") + + +def test_update_entry_notes_set_and_clear(db): + entry = service.add_entry(db, Section.other, "Parking", Decimal("25.00")) + updated = service.update_entry(db, entry.id, notes="work") + assert updated is not None + assert updated.notes == "work" + updated = service.update_entry(db, entry.id, notes="") + assert updated is not None + assert updated.notes is None + service.update_entry(db, entry.id, notes="work again") + updated = service.update_entry(db, entry.id, notes=None) + assert updated is not None + assert updated.notes is None + + +def test_update_entry_all_three_atomic(db): + entry = service.add_entry(db, Section.food, "Groceries", Decimal("400.00")) + updated = service.update_entry( + db, + entry.id, + name="Groceries (Costco)", + amount=Decimal("450.00"), + notes="weekly run", + ) + assert updated is not None + assert updated.name == "Groceries (Costco)" + assert updated.amount == Decimal("450.00") + assert updated.notes == "weekly run" + + +def test_update_entry_notes_untouched_when_sentinel(db): + entry = service.add_entry( + db, Section.other, "Gift", Decimal("25.00"), notes="birthday" + ) + updated = service.update_entry(db, entry.id, amount=Decimal("30.00")) + assert updated is not None + assert updated.notes == "birthday" + + +def test_update_entry_missing_returns_none(db): + assert service.update_entry(db, 9999, name="Whatever") is None + + +def test_update_entry_does_not_mutate_existing_month_snapshot(db): + from quartermaster import month_service + entry = service.add_entry( + db, Section.subscription, "Twitch", Decimal("10.99") + ) + month = month_service.create_month(db, "2026-04") + me = next(e for e in month.entries if e.source_entry_id == entry.id) + assert me.planned == Decimal("10.99") + assert me.origin_planned == Decimal("10.99") + assert me.name == "Twitch" + assert me.origin_name == "Twitch" + + service.update_entry( + db, entry.id, name="Twitch Prime", amount=Decimal("11.99") + ) + db.refresh(me) + assert me.planned == Decimal("10.99") + assert me.origin_planned == Decimal("10.99") + assert me.name == "Twitch" + assert me.origin_name == "Twitch"