from pathlib import Path import asyncio import pytest from app.core.config import settings from app.economy.backend.router import ( SettlementRequest, _build_settlement_preview, _normalise_billing_method, _settlement_label, ) ROOT = Path(__file__).resolve().parents[1] @pytest.fixture(autouse=True) def legacy_queue_mode(monkeypatch): monkeypatch.setattr(settings, "WORKFLOWS_ENABLED", False) def test_time_queue_normalises_legacy_settlement_names(): assert _normalise_billing_method("prepaid_card") == "prepaid" assert _normalise_billing_method("fixed_price") == "subscription" assert _normalise_billing_method(None) == "invoice" assert _settlement_label("subscription") == "Abonnement / fast pris" def test_time_queue_has_preview_and_safe_draft_tracking(): router_source = (ROOT / "app/economy/backend/router.py").read_text() template_source = (ROOT / "app/economy/frontend/time_queue.html").read_text() migration_source = (ROOT / "migrations/1018_economy_time_queue_settlement.sql").read_text() assert '"/time-queue/preview-settlement"' in router_source assert '"/time-queue/settle"' in router_source assert "economy_order_draft_id" in router_source assert "preview-settlement" in template_source assert "Forhåndsvisning af afregning" in template_source assert "economy_order_draft_id" in migration_source def test_case_customer_is_authoritative_for_queue_and_settlement(): router_source = (ROOT / "app/economy/backend/router.py").read_text() # A moved/corrected case must not be invoiced to the historic customer # stored on a time entry. assert "COALESCE(s.customer_id, effective_customer.hub_customer_id) AS customer_id" in router_source assert "The case company is authoritative" in router_source def test_time_queue_includes_solved_and_closed_cases_by_default(monkeypatch): from app.economy.backend import router as economy_router captured = {} def fake_query(query, params): captured["query"] = query captured["params"] = params return [{"id": 8, "case_status": "Løst"}] monkeypatch.setattr(economy_router, "execute_query", fake_query) result = asyncio.run(economy_router.list_hub_time_queue( customer_id=None, status=None, case_state=None, billable=None, q=None, limit=500 )) assert result["items"][0]["case_status"] == "Løst" assert "s.status" not in captured["query"].split("WHERE", 1)[1].split("ORDER BY", 1)[0] def test_time_queue_has_explicit_closed_case_filter(monkeypatch): from app.economy.backend import router as economy_router captured = {} monkeypatch.setattr(economy_router, "execute_query", lambda query, params: captured.update(query=query, params=params) or []) asyncio.run(economy_router.list_hub_time_queue( customer_id=None, status=None, case_state="closed", billable=None, q=None, limit=500 )) assert "'lukket', 'løst', 'closed', 'resolved', 'afsluttet', 'done'" in captured["query"] assert "s.status" not in captured["params"] def test_invoice_preview_uses_unrounded_draft_hours(monkeypatch): from app.economy.backend import router as economy_router monkeypatch.setattr(economy_router, "_selected_time_entries", lambda ids: [{ "id": 12, "customer_id": 44, "sag_id": 7, "billing_method": "invoice", "billable": True, "approved_hours": 0.5, "original_hours": 0.5, "faktisk_tid_min": 18, "case_title": "Support", "customer_name": "Eksempel ApS", }]) monkeypatch.setattr( economy_router, "_resolve_tmodule_customer_id", lambda customer_id, sag_id: 44, ) monkeypatch.setattr( economy_router, "execute_query_single", lambda query, params: {"name": "Eksempel ApS", "hourly_rate": 1000}, ) preview = _build_settlement_preview(SettlementRequest(ids=[12], billing_method="invoice")) assert preview["valid"] is True assert preview["invoice_groups"] == [{ "customer_id": 44, "customer_name": "Eksempel ApS", "entries": [12], "hours": 0.5, "hourly_rate": 1000.0, "amount_ex_vat": 500.0, }]