# Time Tracking - Billed Via Hub Order ID ## Oversigt Implementeret tracking af hvilken Hub ordre (og dermed e-conomic ordre) hver tidsregistrering er blevet faktureret gennem. ## Database Ændringer ### Migration: `060_add_billed_via_thehub_id.sql` Tilføjet nyt felt til `tmodule_times` tabellen: ```sql ALTER TABLE tmodule_times ADD COLUMN billed_via_thehub_id INTEGER; ALTER TABLE tmodule_times ADD CONSTRAINT tmodule_times_billed_via_thehub_id_fkey FOREIGN KEY (billed_via_thehub_id) REFERENCES tmodule_orders(id) ON DELETE SET NULL; CREATE INDEX idx_tmodule_times_billed_via_thehub_id ON tmodule_times(billed_via_thehub_id); ``` **Felt beskrivelse:** - `billed_via_thehub_id`: Hub ordre ID som tidsregistreringen er faktureret gennem - Foreign key til `tmodule_orders.id` - Via Hub ordren kan man finde `economic_order_number` som er ordrenummeret i e-conomic ## Kode Ændringer ### 1. `app/timetracking/backend/models.py` Tilføjet felt til `TModuleTime` Pydantic model: ```python billed_via_thehub_id: Optional[int] = Field(None, description="Hub order ID this time was billed through") is_travel: bool = False # Også tilføjet manglende felt ``` ### 2. `app/timetracking/backend/economic_export.py` Opdateret `export_order_to_economic()` til at sætte `billed_via_thehub_id` når ordren eksporteres: ```python # Marker time entries som billed og opdater billed_via_thehub_id execute_update( """UPDATE tmodule_times SET status = 'billed', billed_via_thehub_id = %s WHERE id IN ( SELECT UNNEST(time_entry_ids) FROM tmodule_order_lines WHERE order_id = %s )""", (request.order_id, request.order_id) ) ``` **Vigtig note:** vTiger opdateres også via `update_timelog_billed()` som sætter `billed_via_thehub_id` i vTiger's Timelog records. ### 3. `app/timetracking/backend/order_service.py` **FJERNET** for tidlig status opdatering: ```python # BEFORE: Time entries blev sat til 'billed' når Hub ordre blev oprettet # AFTER: Time entries forbliver 'approved' indtil e-conomic eksporten er succesfuld ``` Nu opdateres `status='billed'` og `billed_via_thehub_id` KUN i `economic_export.py` efter succesfuld eksport. ### 4. `app/timetracking/backend/vtiger_sync.py` **BESKYTTELSE MOD OVERSKRIVNING:** Tidsregistreringer med `billed_via_thehub_id` opdateres IKKE ved sync: ```python # Update only if NOT yet approved AND NOT yet billed result = execute_update( """UPDATE tmodule_times SET description = %s, original_hours = %s, worked_date = %s, user_name = %s, billable = %s, vtiger_data = %s::jsonb, sync_hash = %s, last_synced_at = CURRENT_TIMESTAMP WHERE vtiger_id = %s AND status = 'pending' AND billed_via_thehub_id IS NULL""", ... ) ``` Dette sikrer at allerede fakturerede tidsregistreringer forbliver låst og ikke overskrevet af vTiger sync. ## Workflow ### Før ændringerne: 1. Godkendte tider → `status='approved'` 2. **Opret ordre** i Hub → `status='billed'` ⚠️ (for tidligt!) 3. Eksporter til e-conomic → `economic_order_number` sættes på Hub ordre ### Efter ændringerne: 1. Godkendte tider → `status='approved'` 2. **Opret ordre** i Hub → tider forbliver `status='approved'` 3. **Eksporter til e-conomic** → `status='billed'` + `billed_via_thehub_id` sættes 4. vTiger opdateres også med `billed_via_thehub_id` ## Data Relations ``` tmodule_times.billed_via_thehub_id ↓ (foreign key) tmodule_orders.id → tmodule_orders.economic_order_number (e-conomic ordre nummer) → tmodule_orders.economic_draft_id (e-conomic kladde ID) ``` ## Queries ### Find alle tidsregistreringer for en e-conomic ordre: ```sql SELECT t.*, o.economic_order_number FROM tmodule_times t JOIN tmodule_orders o ON t.billed_via_thehub_id = o.id WHERE o.economic_order_number = '12345'; ``` ### Find tider der er faktureret via en specifik Hub ordre: ```sql SELECT * FROM tmodule_times WHERE billed_via_thehub_id = 123; ``` ### Find tider der IKKE er faktureret endnu: ```sql SELECT * FROM tmodule_times WHERE status = 'approved' AND billed_via_thehub_id IS NULL; ``` ## vTiger Integration vTiger's `Timelog` records opdateres også via `app/timetracking/backend/vtiger_sync.py`: ```python async def update_timelog_billed(self, vtiger_ids: List[str], hub_order_id: int): payload = { "elementType": "Timelog", "element": { "id": vtiger_id, "billed_via_thehub_id": str(hub_order_id), "cf_timelog_invoiced": "1" } } ``` **Note:** `cf_timelog_invoiced` (IS BILLED) feltet i vTiger kan vi IKKE ændre fra Hub (vTiger begrænsning), men vi opdaterer vores eget `billed_via_thehub_id` felt. ## Testing Migrationen er kørt og verificeret: ```bash docker exec bmc-hub-postgres psql -U bmc_hub -d bmc_hub -c "\d tmodule_times" # Viser: billed_via_thehub_id | integer med foreign key ``` ## Deployment 1. Migration er kørt på dev (060_add_billed_via_thehub_id.sql) 2. Kode ændringer deployed i samme commit 3. Eksisterende tidsregistreringer har `billed_via_thehub_id = NULL` 4. Fremtidige eksporter vil populere feltet korrekt ## Relaterede Filer - `migrations/060_add_billed_via_thehub_id.sql` - `app/timetracking/backend/models.py` - `app/timetracking/backend/economic_export.py` - `app/timetracking/backend/order_service.py` - `app/timetracking/backend/vtiger_sync.py`