"""Time-tab financial edits must use the audited workflow override path.""" import asyncio from importlib import import_module import pytest from fastapi import HTTPException from app.core.config import settings from app.workflows import integration,settlement from app.workflows import decisions,classification router=import_module('app.timetracking.backend.router') @pytest.fixture def active(monkeypatch): monkeypatch.setattr(settings,'WORKFLOWS_ENABLED',True) monkeypatch.setattr(settings,'WORKFLOWS_MODE','active') monkeypatch.setattr(integration,'active_for_customer',lambda customer:customer==24) monkeypatch.setattr(settlement,'workflow_customer',lambda _:24) @pytest.mark.parametrize('payload',[{'billing_method':'prepaid'},{'prepaid_card_id':9},{'fixed_price_agreement_id':8}]) def test_raw_patch_does_not_change_active_workflow_financial_selection(monkeypatch,active,payload): original={'id':1,'vtiger_id':None,'billing_method':'invoice','status':'pending'} monkeypatch.setattr(router,'execute_query_single',lambda *_:original) monkeypatch.setattr(router,'execute_query',lambda *_:pytest.fail('No direct financial selection write permitted')) monkeypatch.setattr(router,'_assert_prepaid_entry_editable',lambda _:None) try: result=asyncio.run(router.patch_time_entry_v1(1,payload,current_user={'id':4,'is_superadmin':True})) except HTTPException as exc: assert exc.status_code==409 else: assert result['billing_method']=='invoice' def test_legacy_wizard_cannot_change_billable_without_audited_override(monkeypatch,active): monkeypatch.setattr(router,'execute_query_single',lambda *_:{'id':1,'vtiger_id':None,'billing_method':'invoice','status':'pending'}) monkeypatch.setattr(router,'execute_query',lambda *_:pytest.fail('Active financial edit must not reach UPDATE')) payload=router.TModuleWizardEditRequest(billable=False) with pytest.raises(HTTPException) as error: asyncio.run(router.update_entry_details(1,payload)) assert error.value.status_code==409 def test_legacy_reset_cannot_reopen_active_workflow_time(monkeypatch,active): monkeypatch.setattr(router,'execute_query_single',lambda *_:{'id':1,'vtiger_id':None,'billing_method':'invoice','status':'approved'}) monkeypatch.setattr(router.wizard,'reset_to_pending',lambda *args,**kwargs:pytest.fail('Workflow time cannot use legacy reset')) with pytest.raises(HTTPException) as error: asyncio.run(router.reset_to_pending(1,reason='Old wizard',user_id=4)) assert error.value.status_code==409 def test_legacy_bulk_approval_cannot_skip_workflow_decision(monkeypatch,active): monkeypatch.setattr(router.wizard,'get_case_entries',lambda *args,**kwargs:[{'id':1,'vtiger_id':None}]) monkeypatch.setattr(router.wizard,'approve_case_entries',lambda *args,**kwargs:pytest.fail('No active workflow bulk approval through legacy wizard')) with pytest.raises(HTTPException) as error: asyncio.run(router.approve_all_case_entries(9,user_id=4)) assert error.value.status_code==409 @pytest.mark.parametrize('workflows_enabled,vtiger_id',[(False,None),(True,'legacy-vtiger')]) def test_wizard_edit_preserves_nonworkflow_and_imported_behavior(monkeypatch,active,workflows_enabled,vtiger_id): monkeypatch.setattr(settings,'WORKFLOWS_ENABLED',workflows_enabled) monkeypatch.setattr(router,'execute_query_single',lambda *_:{'id':1,'vtiger_id':vtiger_id,'billing_method':'invoice','status':'pending'}) writes=[] monkeypatch.setattr(router,'execute_query',lambda sql,params:writes.append((sql,params))) result=asyncio.run(router.update_entry_details(1,router.TModuleWizardEditRequest(billable=False))) assert result['id']==1 assert len(writes)==1 and 'billable = %s' in writes[0][0] def test_manual_override_records_actor_reason_and_previous_decision(monkeypatch): from contextlib import contextmanager captured={} @contextmanager def transaction(): yield object() monkeypatch.setattr(decisions.service,'transaction',transaction) monkeypatch.setattr(decisions,'_locked_time',lambda *args:({'workflow_revision':5},{'id':2})) monkeypatch.setattr(decisions.service,'load_time_context',lambda *args:{'time_id':1,'customer_id':'24','workflow_revision':5}) monkeypatch.setattr(decisions,'active_for_customer',lambda _:True) monkeypatch.setattr(classification,'enrich_time_context',lambda cursor,context:context) def save(cursor,time_id,revision,result,manual=False): captured.update(result=result,manual=manual,time_id=time_id,revision=revision) return result monkeypatch.setattr(decisions,'_save',save) result=decisions.override(1,decisions.OverrideRequest(method='invoice',reason=' Aftale med kunden '),user={'id':4}) assert captured['manual'] is True and captured['revision']==5 assert result['actor_id']==4 and result['previous_decision_id']==2 assert result['reason']=='manual_override' and result['override_reason']=='Aftale med kunden' assert result['method']=='invoice'