295 lines
9.9 KiB
Python
295 lines
9.9 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy import text
|
|
from sqlalchemy.orm import Session
|
|
from typing import Any
|
|
|
|
from app.database import get_session
|
|
from app.auth import get_current_user
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _row(r) -> dict:
|
|
return dict(r._mapping)
|
|
|
|
|
|
@router.get("")
|
|
def list_colonies(
|
|
apiary_id: int | None = Query(default=None),
|
|
include_closed: bool = Query(default=False),
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> list[dict]:
|
|
where = "1=1" if include_closed else "c.closed_at IS NULL"
|
|
params: dict[str, Any] = {}
|
|
if apiary_id is not None:
|
|
where += " AND c.apiary_id = :apiary_id"
|
|
params["apiary_id"] = apiary_id
|
|
|
|
rows = session.execute(text(f"""
|
|
SELECT
|
|
c.*,
|
|
a.name AS apiary_name,
|
|
p.name AS parent_name,
|
|
q.year AS queen_year,
|
|
q.breed AS queen_breed,
|
|
(SELECT MAX(i.date::DATE)
|
|
FROM inspections i
|
|
WHERE i.colony_id = c.id) AS last_inspection_date
|
|
FROM colonies c
|
|
LEFT JOIN apiaries a ON a.id = c.apiary_id
|
|
LEFT JOIN colonies p ON p.id = c.parent_colony_id
|
|
LEFT JOIN queens q ON q.id = c.current_queen_id
|
|
WHERE {where}
|
|
ORDER BY a.name, c.name
|
|
"""), params).fetchall()
|
|
return [_row(r) for r in rows]
|
|
|
|
|
|
@router.get("/log-placeholder", include_in_schema=False)
|
|
def _log_placeholder():
|
|
pass # just to force ordering; actual log route is below
|
|
|
|
|
|
@router.get("/{id}/log")
|
|
def colony_log(
|
|
id: int,
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> list[dict]:
|
|
rows = session.execute(text("""
|
|
SELECT date, type, summary, ref_id, created_at FROM (
|
|
SELECT date, 'durchsicht' AS type,
|
|
TRIM(BOTH FROM CONCAT_WS(' ',
|
|
CASE WHEN brood_frames IS NOT NULL THEN brood_frames::TEXT || ' BW' END,
|
|
CASE WHEN honey_frames IS NOT NULL THEN honey_frames::TEXT || ' HW' END,
|
|
CASE WHEN colony_strength IS NOT NULL THEN 'Stärke ' || colony_strength::TEXT END,
|
|
CASE WHEN queen_seen = 1 THEN '♛ gesehen' END
|
|
)) AS summary,
|
|
id AS ref_id, created_at
|
|
FROM inspections WHERE colony_id = :colony_id
|
|
|
|
UNION ALL
|
|
|
|
SELECT date, 'kontrolle' AS type,
|
|
count::TEXT || ' Milben' ||
|
|
CASE WHEN date_from IS NOT NULL
|
|
THEN ' · ' || (date::DATE - date_from::DATE)::TEXT || ' Tage' ELSE '' END AS summary,
|
|
id AS ref_id, created_at
|
|
FROM varroa_counts WHERE colony_id = :colony_id
|
|
|
|
UNION ALL
|
|
|
|
SELECT date, 'behandlung' AS type,
|
|
product || CASE WHEN dosage IS NOT NULL AND dosage <> ''
|
|
THEN ' · ' || dosage ELSE '' END AS summary,
|
|
id AS ref_id, created_at
|
|
FROM varroa_treatments WHERE colony_id = :colony_id
|
|
|
|
UNION ALL
|
|
|
|
SELECT vc.date, 'nachkontrolle' AS type,
|
|
vt.product || CASE WHEN vc.result IS NOT NULL AND vc.result <> ''
|
|
THEN ': ' || vc.result ELSE '' END AS summary,
|
|
vc.id AS ref_id, vc.created_at
|
|
FROM varroa_controls vc
|
|
JOIN varroa_treatments vt ON vt.id = vc.treatment_id
|
|
WHERE vc.colony_id = :colony_id
|
|
|
|
UNION ALL
|
|
|
|
SELECT h.date, 'ernte' AS type,
|
|
h.weight_kg::TEXT || ' kg' ||
|
|
CASE WHEN h.type IS NOT NULL THEN ' · ' || h.type ELSE '' END AS summary,
|
|
h.id AS ref_id, h.created_at
|
|
FROM honey_harvests h WHERE h.colony_id = :colony_id
|
|
|
|
UNION ALL
|
|
|
|
SELECT ca.date, 'massnahme' AS type,
|
|
CASE ca.type
|
|
WHEN 'ablegerbildung' THEN 'Ablegerbildung'
|
|
WHEN 'zwischenbodenableger' THEN 'Zwischenbödenableger'
|
|
WHEN 'brutentnahme' THEN 'Brutentnahme'
|
|
WHEN 'standwechsel' THEN 'Standwechsel'
|
|
WHEN 'futterung' THEN 'Fütterung'
|
|
WHEN 'vereinigung' THEN 'Völkervereinigung'
|
|
ELSE 'Maßnahme'
|
|
END ||
|
|
CASE WHEN ca.notes IS NOT NULL AND ca.notes <> ''
|
|
THEN ': ' || ca.notes ELSE '' END AS summary,
|
|
ca.id AS ref_id, ca.created_at
|
|
FROM colony_actions ca WHERE ca.colony_id = :colony_id
|
|
) log
|
|
ORDER BY date::DATE DESC, created_at DESC
|
|
"""), {"colony_id": id}).fetchall()
|
|
return [_row(r) for r in rows]
|
|
|
|
|
|
@router.get("/{id}/lineage")
|
|
def colony_lineage(
|
|
id: int,
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> list[dict]:
|
|
rows = session.execute(text("""
|
|
WITH RECURSIVE lineage(id, name, status, parent_colony_id, depth) AS (
|
|
SELECT id, name, status, parent_colony_id, 0
|
|
FROM colonies WHERE id = :colony_id
|
|
UNION ALL
|
|
SELECT c.id, c.name, c.status, c.parent_colony_id, l.depth + 1
|
|
FROM colonies c
|
|
JOIN lineage l ON c.id = l.parent_colony_id
|
|
)
|
|
SELECT * FROM lineage ORDER BY depth DESC
|
|
"""), {"colony_id": id}).fetchall()
|
|
return [_row(r) for r in rows]
|
|
|
|
|
|
@router.get("/{id}/descendants")
|
|
def colony_descendants(
|
|
id: int,
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> list[dict]:
|
|
rows = session.execute(text("""
|
|
WITH RECURSIVE descendants(id, name, status, parent_colony_id, depth) AS (
|
|
SELECT id, name, status, parent_colony_id, 0
|
|
FROM colonies WHERE id = :colony_id
|
|
UNION ALL
|
|
SELECT c.id, c.name, c.status, c.parent_colony_id, d.depth + 1
|
|
FROM colonies c
|
|
JOIN descendants d ON c.parent_colony_id = d.id
|
|
)
|
|
SELECT * FROM descendants WHERE depth > 0 ORDER BY depth, name
|
|
"""), {"colony_id": id}).fetchall()
|
|
return [_row(r) for r in rows]
|
|
|
|
|
|
@router.get("/{id}/location-history")
|
|
def colony_location_history(
|
|
id: int,
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> list[dict]:
|
|
rows = session.execute(text("""
|
|
SELECT cl.*, a.name AS apiary_name
|
|
FROM colony_locations cl
|
|
JOIN apiaries a ON a.id = cl.apiary_id
|
|
WHERE cl.colony_id = :colony_id
|
|
ORDER BY cl.moved_at DESC
|
|
"""), {"colony_id": id}).fetchall()
|
|
return [_row(r) for r in rows]
|
|
|
|
|
|
@router.get("/{id}")
|
|
def get_colony(
|
|
id: int,
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> dict:
|
|
row = session.execute(text("""
|
|
SELECT c.*, a.name AS apiary_name, p.name AS parent_name
|
|
FROM colonies c
|
|
LEFT JOIN apiaries a ON a.id = c.apiary_id
|
|
LEFT JOIN colonies p ON p.id = c.parent_colony_id
|
|
WHERE c.id = :id
|
|
"""), {"id": id}).fetchone()
|
|
if row is None:
|
|
raise HTTPException(status_code=404, detail="Not found")
|
|
return _row(row)
|
|
|
|
|
|
@router.post("", status_code=201)
|
|
def create_colony(
|
|
body: dict[str, Any],
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> dict:
|
|
row = session.execute(text("""
|
|
INSERT INTO colonies (name, apiary_id, status, parent_colony_id, notes)
|
|
VALUES (:name, :apiary_id, :status, :parent_colony_id, :notes)
|
|
RETURNING *
|
|
"""), {
|
|
"name": body.get("name"),
|
|
"apiary_id": body.get("apiary_id"),
|
|
"status": body.get("status", "wirtschaftsvolk"),
|
|
"parent_colony_id": body.get("parent_colony_id"),
|
|
"notes": body.get("notes"),
|
|
}).fetchone()
|
|
colony_id = row._mapping["id"]
|
|
session.execute(text("""
|
|
INSERT INTO colony_locations (colony_id, apiary_id) VALUES (:colony_id, :apiary_id)
|
|
"""), {"colony_id": colony_id, "apiary_id": body.get("apiary_id")})
|
|
session.commit()
|
|
return _row(row)
|
|
|
|
|
|
@router.put("/{id}")
|
|
def update_colony(
|
|
id: int,
|
|
body: dict[str, Any],
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> dict:
|
|
# Check if apiary changed (to insert colony_location)
|
|
old = session.execute(
|
|
text("SELECT apiary_id FROM colonies WHERE id = :id"), {"id": id}
|
|
).fetchone()
|
|
|
|
result = session.execute(text("""
|
|
UPDATE colonies
|
|
SET name = :name, apiary_id = :apiary_id, status = :status, notes = :notes
|
|
WHERE id = :id
|
|
RETURNING *
|
|
"""), {
|
|
"id": id,
|
|
"name": body.get("name"),
|
|
"apiary_id": body.get("apiary_id"),
|
|
"status": body.get("status"),
|
|
"notes": body.get("notes"),
|
|
}).fetchone()
|
|
if result is None:
|
|
raise HTTPException(status_code=404, detail="Not found")
|
|
|
|
new_apiary_id = body.get("apiary_id")
|
|
if old is not None and old._mapping["apiary_id"] != new_apiary_id:
|
|
session.execute(text("""
|
|
INSERT INTO colony_locations (colony_id, apiary_id) VALUES (:colony_id, :apiary_id)
|
|
"""), {"colony_id": id, "apiary_id": new_apiary_id})
|
|
|
|
session.commit()
|
|
return _row(result)
|
|
|
|
|
|
@router.post("/{id}/close")
|
|
def close_colony(
|
|
id: int,
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> dict:
|
|
result = session.execute(text("""
|
|
UPDATE colonies SET status = 'eingegangen', closed_at = NOW()
|
|
WHERE id = :id RETURNING *
|
|
"""), {"id": id}).fetchone()
|
|
session.commit()
|
|
if result is None:
|
|
raise HTTPException(status_code=404, detail="Not found")
|
|
return _row(result)
|
|
|
|
|
|
@router.post("/{id}/set-queen")
|
|
def set_queen(
|
|
id: int,
|
|
body: dict[str, Any],
|
|
session: Session = Depends(get_session),
|
|
_: str = Depends(get_current_user),
|
|
) -> dict:
|
|
result = session.execute(text("""
|
|
UPDATE colonies SET current_queen_id = :queen_id WHERE id = :id RETURNING *
|
|
"""), {"id": id, "queen_id": body.get("queen_id")}).fetchone()
|
|
session.commit()
|
|
if result is None:
|
|
raise HTTPException(status_code=404, detail="Not found")
|
|
return _row(result)
|