bmc_hub/docs/TIMETRACKING_BILLED_VIA_HUB.md

180 lines
5.3 KiB
Markdown

# 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`