from datetime import date from decimal import Decimal import pytest from django.conf import settings from finance.enums import PayrollPeriodStatus from finance.models import PayrollAuditEvent, PayrollPeriod from finance.payroll.carryovers import create_carryover from finance.tasks import ensure_monthly_payroll_period, open_monthly_payroll_period pytestmark = pytest.mark.django_db def test_monthly_period_task_is_scheduled_for_first_day(): task = settings.CELERY_BEAT_SCHEDULE["open-monthly-payroll-period"] assert task["task"] == "finance.tasks.open_monthly_payroll_period" assert task["schedule"].day_of_month == {1} assert task["schedule"].hour == {0} assert task["schedule"].minute == {5} def test_ensure_monthly_payroll_period_opens_full_calendar_month(): period, created = ensure_monthly_payroll_period(date(2026, 8, 1)) assert created is True assert period.code == "2026-08" assert period.name == "Август 2026" assert period.date_from == date(2026, 8, 1) assert period.date_to == date(2026, 8, 31) assert period.pay_date is None assert period.currency == "RUB" assert period.status == PayrollPeriodStatus.OPEN assert period.created_by is None event = PayrollAuditEvent.objects.get( action="payroll.period_created", period=period, ) assert event.actor is None assert event.metadata == {"channel": "automation", "schedule": "monthly"} def test_ensure_monthly_payroll_period_is_idempotent(): first, first_created = ensure_monthly_payroll_period(date(2028, 2, 1)) second, second_created = ensure_monthly_payroll_period(date(2028, 2, 15)) assert first_created is True assert second_created is False assert second.pk == first.pk assert first.date_to == date(2028, 2, 29) assert PayrollPeriod.objects.count() == 1 assert ( PayrollAuditEvent.objects.filter( action="payroll.period_created", period=first, ).count() == 1 ) def test_monthly_task_uses_local_current_month(monkeypatch): monkeypatch.setattr("finance.tasks.timezone.localdate", lambda: date(2026, 9, 1)) result = open_monthly_payroll_period() period = PayrollPeriod.objects.get(pk=result["period_id"]) assert result == {"period_id": period.pk, "code": "2026-09", "created": True} assert period.date_from == date(2026, 9, 1) assert period.date_to == date(2026, 9, 30) def test_monthly_period_opening_does_not_materialize_legacy_manual_carryover( user_factory, ): manager = user_factory(email="period.carryover.manager@example.test") employee = user_factory(email="period.carryover.employee@example.test") source = PayrollPeriod.objects.create( code="2026-07", name="Июль 2026", date_from=date(2026, 7, 1), date_to=date(2026, 7, 31), currency="RUB", status=PayrollPeriodStatus.CLOSED, created_by=manager, ) carryover = create_carryover( source_period=source, employee=employee, direction="company_owes", amount=Decimal("700.00"), reason="Остаток июля", actor=manager, ) assert carryover.target_period_id is None target, created = ensure_monthly_payroll_period(date(2026, 8, 1)) assert created is True carryover.refresh_from_db() assert target.status == PayrollPeriodStatus.OPEN assert carryover.target_period_id is None assert carryover.generated_input_line_id is None