from datetime import datetime, time, timedelta, timezone as datetime_timezone import pytest from django.urls import reverse from django.utils import timezone from apps.payments import policies, selectors, services from apps.payments.forms import ReceiptForm from apps.payments.models import ( PaymentAccessConfiguration, PaymentApprovalPolicy, PaymentNotice, PaymentReceipt, PaymentReceiptRevision, ReceiptApprovalCycle, ) from tests.test_payments import case, data, grant, submit, upload pytestmark = pytest.mark.django_db def test_daily_summary_uses_current_amounts_final_outcomes_and_all_pending(case, django_assert_num_queries): independent_policy = PaymentApprovalPolicy.objects.create(name='بررسی فیش مستقل') independent_policy.reviewers.add(case['first'], case['second']) approved = submit(case) services.decide_receipt(case['first'], approved.public_id, 1, 'APPROVE') services.decide_receipt(case['second'], approved.public_id, 1, 'APPROVE') rejected = submit(case, linked=False) services.decide_receipt(case['first'], rejected.public_id, 1, 'REJECT', 'ناخوانا') partial = submit(case) services.decide_receipt(case['first'], partial.public_id, 1, 'APPROVE') pending = submit(case, linked=False) revised = submit(case) services.decide_receipt(case['first'], revised.public_id, 1, 'REJECT', 'اصلاح مبلغ') form = ReceiptForm(data(case, paid_amount='9000'), {'receipt_image': upload()}, customer=case['customer']) assert form.is_valid(), form.errors services.revise_receipt(case['customer'], revised.public_id, 1, form.cleaned_data) PaymentReceiptRevision.objects.filter(version=1).update(amount=100) yesterday = timezone.now() - timedelta(days=1) PaymentReceipt.objects.filter(pk__in=[approved.pk, pending.pk]).update(created_at=yesterday) # The written bank date is unrelated to when evidence arrived or was reviewed. PaymentReceiptRevision.objects.update(paid_date=yesterday.date()) with django_assert_num_queries(2): summary = selectors.daily_payment_summary( PaymentReceipt.objects.all(), PaymentNotice.objects.filter(deleted_at__isnull=True)) assert summary == { 'date': timezone.localdate(), 'submitted_count': 3, 'submitted_amount': 9200, 'approved_count': 1, 'approved_amount': 100, 'rejected_count': 1, 'rejected_amount': 100, 'pending_count': 3, 'pending_amount': 9200, 'notice_count': 1, } @pytest.mark.parametrize('offset,included', [ (-1, False), (0, True), (24 * 60 * 60 - 1, True), (24 * 60 * 60, False), ]) def test_daily_summary_tehran_midnight_boundaries(case, monkeypatch, offset, included): receipt = submit(case) services.decide_receipt(case['senior'], receipt.public_id, 1, 'SENIOR_APPROVE', 'بررسی نهایی') today = timezone.localdate() start = timezone.make_aware(datetime.combine(today, time.min), timezone.get_default_timezone()) stamp = start + timedelta(seconds=offset) PaymentReceipt.objects.filter(pk=receipt.pk).update(created_at=stamp) PaymentNotice.objects.filter(pk=case['notice'].pk).update(created_at=stamp) ReceiptApprovalCycle.objects.filter(revision__receipt=receipt).update(finalized_at=stamp) monkeypatch.setattr(timezone, 'now', lambda: start + timedelta(hours=1)) # An active display timezone must not move the Tehran business-day boundary. with timezone.override(datetime_timezone.utc): summary = selectors.daily_payment_summary(PaymentReceipt.objects.all(), PaymentNotice.objects.all()) assert summary['date'] == today assert summary['submitted_count'] == summary['approved_count'] == summary['notice_count'] == int(included) assert summary['submitted_amount'] == summary['approved_amount'] assert bool(summary['submitted_amount']) == included def test_daily_summary_excludes_old_finalizations_and_deleted_notices(case): receipt = submit(case) services.decide_receipt(case['first'], receipt.public_id, 1, 'REJECT', 'ناخوانا') ReceiptApprovalCycle.objects.filter(revision__receipt=receipt).update( finalized_at=timezone.now() - timedelta(days=1)) PaymentNotice.objects.filter(pk=case['notice'].pk).update(deleted_at=timezone.now()) summary = selectors.daily_payment_summary( policies.visible(PaymentReceipt.objects.all(), case['creator']), policies.visible(PaymentNotice.objects.all(), case['creator'])) assert summary['submitted_count'] == 1 assert summary['rejected_count'] == summary['rejected_amount'] == summary['notice_count'] == 0 def test_daily_summary_permission_scope_and_zero_state(case, client): submit(case) PaymentAccessConfiguration.objects.update_or_create(pk=1, defaults={'open_view': False}) client.force_login(case['outsider']) response = client.get(reverse('staff_payments:desk')) assert response.status_code == 200 summary = response.context['daily_summary'] assert all(value == 0 for key, value in summary.items() if key != 'date') grant(case['outsider'], 'view_paymentreceipt') response = client.get(reverse('staff_payments:desk')) assert response.context['daily_summary']['submitted_count'] == 1 assert response.context['daily_summary']['notice_count'] == 0 grant(case['outsider'], 'view_paymentnotice') assert client.get(reverse('staff_payments:desk')).context['daily_summary']['notice_count'] == 1 def test_daily_summary_stays_independent_of_tabs_search_and_pagination(case, client): submit(case) client.force_login(case['creator']) endpoint = reverse('staff_payments:desk') baseline = client.get(endpoint).context['daily_summary'] for parameters in [ {'tab': 'notices', 'notice_state': 'cancelled'}, {'tab': 'receipts', 'mine': '1', 'q': 'no-such-customer', 'page': '99'}, {'tab': 'receipts', 'status': 'APPROVED', 'kind': 'independent', 'from': '2000-01-01', 'to': '2000-01-02'}, ]: response = client.get(endpoint, parameters) assert response.status_code == 200 assert response.context['daily_summary'] == baseline assert response.context['page_obj'].paginator.count == 0 assert 'مبنای آمار' in response.content.decode()