Release v2.2.48: sag sale-item fallback and mission webhook ping fixes
This commit is contained in:
parent
9fc57feda4
commit
1323320fed
21
RELEASE_NOTES_v2.2.48.md
Normal file
21
RELEASE_NOTES_v2.2.48.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Release Notes v2.2.48
|
||||
|
||||
Dato: 4. marts 2026
|
||||
|
||||
## Fixes
|
||||
- `sag` aggregering fejler ikke længere hvis tabellen `sag_salgsvarer` mangler; API returnerer fortsat tidsdata og tom salgsliste i stedet for `500`.
|
||||
- Salgsliste-endpoints i `sag` returnerer nu tom liste med advarsel i log, hvis `sag_salgsvarer` ikke findes.
|
||||
- Mission webhooks for `answered` og `hangup` accepterer nu også token-only `GET` ping uden `call_id` (samme kompatibilitet som `ringing`).
|
||||
|
||||
## Ændrede filer
|
||||
- `app/modules/sag/backend/router.py`
|
||||
- `app/dashboard/backend/mission_router.py`
|
||||
- `VERSION`
|
||||
- `RELEASE_NOTES_v2.2.48.md`
|
||||
|
||||
## Drift
|
||||
- Deploy: `./updateto.sh v2.2.48`
|
||||
- Valider webhook ping:
|
||||
- `curl -i "http://localhost:8001/api/v1/mission/webhook/telefoni/ringing?token=<TOKEN>"`
|
||||
- `curl -i "http://localhost:8001/api/v1/mission/webhook/telefoni/answered?token=<TOKEN>"`
|
||||
- `curl -i "http://localhost:8001/api/v1/mission/webhook/telefoni/hangup?token=<TOKEN>"`
|
||||
@ -268,6 +268,12 @@ async def mission_telefoni_answered(event: MissionCallEvent, request: Request, t
|
||||
|
||||
@router.get("/mission/webhook/telefoni/answered")
|
||||
async def mission_telefoni_answered_get(request: Request, token: Optional[str] = Query(None)):
|
||||
_validate_mission_webhook_token(request, token)
|
||||
|
||||
if not _first_query_param(request, "call_id", "callid", "id", "session_id", "uuid"):
|
||||
logger.info("✅ Mission webhook answered ping method=%s", request.method)
|
||||
return {"status": "ok", "mode": "ping"}
|
||||
|
||||
event = _event_from_query(request)
|
||||
return await mission_telefoni_answered(event, request, token)
|
||||
|
||||
@ -311,6 +317,12 @@ async def mission_telefoni_hangup(event: MissionCallEvent, request: Request, tok
|
||||
|
||||
@router.get("/mission/webhook/telefoni/hangup")
|
||||
async def mission_telefoni_hangup_get(request: Request, token: Optional[str] = Query(None)):
|
||||
_validate_mission_webhook_token(request, token)
|
||||
|
||||
if not _first_query_param(request, "call_id", "callid", "id", "session_id", "uuid"):
|
||||
logger.info("📴 Mission webhook hangup ping method=%s", request.method)
|
||||
return {"status": "ok", "mode": "ping"}
|
||||
|
||||
event = _event_from_query(request)
|
||||
return await mission_telefoni_hangup(event, request, token)
|
||||
|
||||
|
||||
@ -26,6 +26,11 @@ logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _table_exists(table_name: str) -> bool:
|
||||
row = execute_query_single("SELECT to_regclass(%s) AS table_name", (f"public.{table_name}",))
|
||||
return bool(row and row.get("table_name"))
|
||||
|
||||
|
||||
def _get_user_id_from_request(request: Request) -> int:
|
||||
user_id = getattr(request.state, "user_id", None)
|
||||
if user_id is not None:
|
||||
@ -213,6 +218,10 @@ async def list_all_sale_items(
|
||||
):
|
||||
"""List all sale items across cases (orders overview)."""
|
||||
try:
|
||||
if not _table_exists("sag_salgsvarer"):
|
||||
logger.warning("⚠️ sag_salgsvarer table missing - returning empty sale items list")
|
||||
return []
|
||||
|
||||
query = """
|
||||
SELECT si.*, s.titel AS sag_titel, s.customer_id, c.name AS customer_name
|
||||
FROM sag_salgsvarer si
|
||||
@ -1205,6 +1214,10 @@ async def get_varekob_salg(sag_id: int, include_subcases: bool = True):
|
||||
if not check:
|
||||
raise HTTPException(status_code=404, detail="Case not found")
|
||||
|
||||
has_sale_items_table = _table_exists("sag_salgsvarer")
|
||||
if not has_sale_items_table:
|
||||
logger.warning("⚠️ sag_salgsvarer table missing - sale item aggregation skipped for sag_id=%s", sag_id)
|
||||
|
||||
if include_subcases:
|
||||
case_tree_query = """
|
||||
WITH RECURSIVE normalized_relations AS (
|
||||
@ -1268,36 +1281,39 @@ async def get_varekob_salg(sag_id: int, include_subcases: bool = True):
|
||||
"""
|
||||
time_entries = execute_query(time_query, (sag_id,))
|
||||
|
||||
sale_items_query = """
|
||||
WITH RECURSIVE normalized_relations AS (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN LOWER(relationstype) IN ('afledt af', 'afledt_af') THEN målsag_id
|
||||
WHEN LOWER(relationstype) IN ('årsag til', 'årsag_til') THEN kilde_sag_id
|
||||
ELSE kilde_sag_id
|
||||
END AS parent_id,
|
||||
CASE
|
||||
WHEN LOWER(relationstype) IN ('afledt af', 'afledt_af') THEN kilde_sag_id
|
||||
WHEN LOWER(relationstype) IN ('årsag til', 'årsag_til') THEN målsag_id
|
||||
ELSE målsag_id
|
||||
END AS child_id
|
||||
FROM sag_relationer
|
||||
WHERE deleted_at IS NULL
|
||||
),
|
||||
case_tree AS (
|
||||
SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL
|
||||
UNION
|
||||
SELECT nr.child_id
|
||||
FROM normalized_relations nr
|
||||
JOIN case_tree ct ON nr.parent_id = ct.id
|
||||
)
|
||||
SELECT si.*, s.titel AS source_sag_titel
|
||||
FROM sag_salgsvarer si
|
||||
JOIN case_tree ct ON si.sag_id = ct.id
|
||||
LEFT JOIN sag_sager s ON s.id = si.sag_id
|
||||
ORDER BY si.line_date DESC NULLS LAST, si.id DESC
|
||||
"""
|
||||
sale_items = execute_query(sale_items_query, (sag_id,))
|
||||
if has_sale_items_table:
|
||||
sale_items_query = """
|
||||
WITH RECURSIVE normalized_relations AS (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN LOWER(relationstype) IN ('afledt af', 'afledt_af') THEN målsag_id
|
||||
WHEN LOWER(relationstype) IN ('årsag til', 'årsag_til') THEN kilde_sag_id
|
||||
ELSE kilde_sag_id
|
||||
END AS parent_id,
|
||||
CASE
|
||||
WHEN LOWER(relationstype) IN ('afledt af', 'afledt_af') THEN kilde_sag_id
|
||||
WHEN LOWER(relationstype) IN ('årsag til', 'årsag_til') THEN målsag_id
|
||||
ELSE målsag_id
|
||||
END AS child_id
|
||||
FROM sag_relationer
|
||||
WHERE deleted_at IS NULL
|
||||
),
|
||||
case_tree AS (
|
||||
SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL
|
||||
UNION
|
||||
SELECT nr.child_id
|
||||
FROM normalized_relations nr
|
||||
JOIN case_tree ct ON nr.parent_id = ct.id
|
||||
)
|
||||
SELECT si.*, s.titel AS source_sag_titel
|
||||
FROM sag_salgsvarer si
|
||||
JOIN case_tree ct ON si.sag_id = ct.id
|
||||
LEFT JOIN sag_sager s ON s.id = si.sag_id
|
||||
ORDER BY si.line_date DESC NULLS LAST, si.id DESC
|
||||
"""
|
||||
sale_items = execute_query(sale_items_query, (sag_id,))
|
||||
else:
|
||||
sale_items = []
|
||||
else:
|
||||
case_tree = execute_query(
|
||||
"SELECT id, titel FROM sag_sager WHERE id = %s AND deleted_at IS NULL",
|
||||
@ -1312,14 +1328,17 @@ async def get_varekob_salg(sag_id: int, include_subcases: bool = True):
|
||||
"""
|
||||
time_entries = execute_query(time_query, (sag_id,))
|
||||
|
||||
sale_items_query = """
|
||||
SELECT si.*, s.titel AS source_sag_titel
|
||||
FROM sag_salgsvarer si
|
||||
LEFT JOIN sag_sager s ON s.id = si.sag_id
|
||||
WHERE si.sag_id = %s
|
||||
ORDER BY si.line_date DESC NULLS LAST, si.id DESC
|
||||
"""
|
||||
sale_items = execute_query(sale_items_query, (sag_id,))
|
||||
if has_sale_items_table:
|
||||
sale_items_query = """
|
||||
SELECT si.*, s.titel AS source_sag_titel
|
||||
FROM sag_salgsvarer si
|
||||
LEFT JOIN sag_sager s ON s.id = si.sag_id
|
||||
WHERE si.sag_id = %s
|
||||
ORDER BY si.line_date DESC NULLS LAST, si.id DESC
|
||||
"""
|
||||
sale_items = execute_query(sale_items_query, (sag_id,))
|
||||
else:
|
||||
sale_items = []
|
||||
|
||||
total_entries = len(time_entries or [])
|
||||
total_hours = 0
|
||||
@ -1497,6 +1516,10 @@ async def list_sale_items(sag_id: int):
|
||||
if not check:
|
||||
raise HTTPException(status_code=404, detail="Case not found")
|
||||
|
||||
if not _table_exists("sag_salgsvarer"):
|
||||
logger.warning("⚠️ sag_salgsvarer table missing - returning empty sale items list for sag_id=%s", sag_id)
|
||||
return []
|
||||
|
||||
query = """
|
||||
SELECT si.*, s.titel AS source_sag_titel
|
||||
FROM sag_salgsvarer si
|
||||
|
||||
Loading…
Reference in New Issue
Block a user