From 8a36652bc89b98e2304b9ed4a32a31005e9d8a2d Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 8 Sep 2026 21:31:56 -0300 Subject: [PATCH] fix: order reconciliation statuses by recency --- finance/reconciliation.py | 16 +++++--- tests/finance/test_reconciliation.py | 59 +++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/finance/reconciliation.py b/finance/reconciliation.py index 0edd863..4969349 100644 --- a/finance/reconciliation.py +++ b/finance/reconciliation.py @@ -5,8 +5,8 @@ from datetime import date from decimal import Decimal from uuid import UUID -from django.db.models import DecimalField, Sum, Value -from django.db.models.functions import Coalesce +from django.db.models import DecimalField, F, Sum, Value, Window +from django.db.models.functions import Coalesce, RowNumber from finance.models import ReconciliationCheckpoint, Transaction @@ -82,7 +82,13 @@ def latest_checkpoint_statuses(currency: str) -> list[CheckpointStatus]: checkpoints = ReconciliationCheckpoint.objects.filter( account__archived=False, account__currency=currency, - ).select_related("account").order_by( - "account_id", "-as_of_date", "-created_at", "-id" - ).distinct("account_id") + ).annotate( + account_rank=Window( + expression=RowNumber(), + partition_by=[F("account_id")], + order_by=["-as_of_date", "-created_at", "-id"], + ) + ).filter(account_rank=1).select_related("account").order_by( + "-as_of_date", "-created_at", "-id" + ) return [checkpoint_status(checkpoint) for checkpoint in checkpoints] diff --git a/tests/finance/test_reconciliation.py b/tests/finance/test_reconciliation.py index 8e4dd41..0f1faf5 100644 --- a/tests/finance/test_reconciliation.py +++ b/tests/finance/test_reconciliation.py @@ -1,11 +1,13 @@ """Synthetic checks for immutable, audited reconciliation checkpoints.""" -from datetime import date +from datetime import date, datetime from decimal import Decimal +from uuid import UUID import pytest +from django.utils import timezone -from finance.models import AuditEvent, ReconciliationCheckpoint +from finance.models import Account, AuditEvent, ReconciliationCheckpoint from finance.reconciliation import ( checkpoint_history, latest_checkpoint_statuses, @@ -254,3 +256,56 @@ def test_latest_checkpoint_statuses_select_by_as_of_date_then_creation_for_activ by_date.pk: newer_as_of_date.pk, by_creation.pk: correction.pk, } + + +def test_latest_checkpoint_statuses_order_selected_accounts_by_checkpoint_recency(): + oldest_account = Account.objects.create( + id=UUID("00000000-0000-0000-0000-000000000001"), + name="Synthetic oldest account", + kind="BANK", + ) + older_same_date_account = Account.objects.create( + id=UUID("00000000-0000-0000-0000-000000000002"), + name="Synthetic older same-date account", + kind="BANK", + ) + newest_account = Account.objects.create( + id=UUID("00000000-0000-0000-0000-000000000003"), + name="Synthetic newest account", + kind="BANK", + ) + oldest = create_reconciliation_checkpoint( + account_id=oldest_account.pk, + as_of_date=AS_OF_DATE, + institution_balance=Decimal("0.00"), + actor=ACTOR, + ) + older_same_date = create_reconciliation_checkpoint( + account_id=older_same_date_account.pk, + as_of_date=date(2026, 9, 2), + institution_balance=Decimal("0.00"), + actor=ACTOR, + ) + newest = create_reconciliation_checkpoint( + account_id=newest_account.pk, + as_of_date=date(2026, 9, 2), + institution_balance=Decimal("0.00"), + actor=ACTOR, + ) + ReconciliationCheckpoint.objects.filter(pk=oldest.pk).update( + created_at=timezone.make_aware(datetime(2026, 9, 1, 9, 0)) + ) + ReconciliationCheckpoint.objects.filter(pk=older_same_date.pk).update( + created_at=timezone.make_aware(datetime(2026, 9, 1, 10, 0)) + ) + ReconciliationCheckpoint.objects.filter(pk=newest.pk).update( + created_at=timezone.make_aware(datetime(2026, 9, 1, 11, 0)) + ) + + statuses = latest_checkpoint_statuses("BRL") + + assert [status.checkpoint_id for status in statuses] == [ + newest.pk, + older_same_date.pk, + oldest.pk, + ]