- Implement tests to ensure the case tree contains parent and child branches while preventing cycles. - Verify that the timeline UI utilizes tree filters and cursor paging. - Confirm that the relation tree is displayed as a separate tab with appropriate search filters and inline module content. Enhance time tracking with prepaid card usage alerts - Create a migration to track alerts for prepaid card usage at 80% and 100%. - Implement triggers to generate cases when usage thresholds are reached, ensuring proper notifications and case management. Introduce audit logging for time entry edits - Modify the sync log to include time entry updates in the audit stream without altering existing records. Establish tracking for vTiger case imports and exports - Create tables to track cases imported from vTiger into Hub and exports from Hub to vTiger, ensuring idempotency and proper reference management.
113 lines
4.0 KiB
Python
113 lines
4.0 KiB
Python
from pathlib import Path
|
|
import asyncio
|
|
|
|
from app.economy.backend.router import (
|
|
SettlementRequest,
|
|
_build_settlement_preview,
|
|
_normalise_billing_method,
|
|
_settlement_label,
|
|
)
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
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,
|
|
}]
|