Files
hms-licht-ton/backend/app/services/rentman_service.py
T

147 lines
6.1 KiB
Python
Raw Normal View History

"""Rentman API client wrapper for equipment import and request submission."""
import httpx
import logging
import re
from typing import Any
from app.config import get_settings
logger = logging.getLogger(__name__)
settings = get_settings()
RENTMAN_BASE_URL = "https://api.rentman.net"
class RentmanService:
"""Wrapper around the Rentman REST API using httpx."""
def __init__(self, token: str | None = None) -> None:
self._token = token or settings.rentman_api_token
self._base_url = RENTMAN_BASE_URL
def _headers(self) -> dict[str, str]:
return {
"Authorization": f"Bearer {self._token}",
"Content-Type": "application/json",
}
async def get_equipment_page(self, limit: int = 100, offset: int = 0) -> dict[str, Any]:
"""Fetch a single page of equipment from Rentman.
Returns the raw JSON response dict with 'data' and optional 'itemCount'.
"""
url = f"{self._base_url}/equipment"
params = {"limit": limit, "offset": offset}
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.get(url, headers=self._headers(), params=params)
resp.raise_for_status()
return resp.json()
async def get_all_equipment(self, limit: int = 100) -> list[dict[str, Any]]:
"""Paginate through all equipment pages until data is empty."""
all_items: list[dict[str, Any]] = []
offset = 0
while True:
page = await self.get_equipment_page(limit=limit, offset=offset)
data = page.get("data", [])
if not data:
break
all_items.extend(data)
offset += limit
return all_items
async def create_project_request(self, payload: dict[str, Any]) -> dict[str, Any]:
"""POST /projectrequests to create a new project request in Rentman."""
url = f"{self._base_url}/projectrequests"
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(url, headers=self._headers(), json=payload)
resp.raise_for_status()
return resp.json()
async def add_equipment_to_request(
self, request_id: str, equipment_payload: dict[str, Any]
) -> dict[str, Any]:
"""POST /projectrequests/{id}/projectrequestequipment for a single item."""
url = f"{self._base_url}/projectrequests/{request_id}/projectrequestequipment"
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(url, headers=self._headers(), json=equipment_payload)
resp.raise_for_status()
return resp.json()
@staticmethod
def transform_equipment(raw: dict[str, Any]) -> dict[str, Any]:
"""Map a raw Rentman equipment object to equipment_cache schema."""
images = raw.get("images") or raw.get("files") or []
if isinstance(images, list):
image_urls = [
img.get("url", img.get("filename", "")) if isinstance(img, dict) else str(img)
for img in images
]
else:
image_urls = []
group = raw.get("equipment_group") or {}
category = group.get("name", "") if isinstance(group, dict) else str(group or "")
return {
"rentman_id": str(raw.get("id", "")),
"name": raw.get("name", ""),
"number": raw.get("number") or raw.get("code", ""),
"category": category,
"subcategory": raw.get("subcategory", ""),
"description": raw.get("description", ""),
"specifications": raw.get("specifications", {}),
"images": image_urls,
"rental_price": raw.get("rental_price"),
"brand": raw.get("brand", ""),
"available": raw.get("available", True),
}
@staticmethod
def build_project_request_payload(data: dict[str, Any]) -> dict[str, Any]:
"""Map frontend rental request data to Rentman POST /projectrequests payload."""
date_start = data.get("date_start")
date_end = data.get("date_end")
contact_name = data.get("contact_name", "")
parts = contact_name.strip().split(" ", 1)
first_name = parts[0] if parts else ""
last_name = parts[1] if len(parts) > 1 else ""
street = data.get("contact_street", "")
house_number = ""
if street:
match = re.search(r"(\d+[a-zA-Z]*)", street)
if match:
house_number = match.group(1)
return {
"name": data.get("event_name", ""),
"planperiod_start": f"{date_start}T08:00:00+02:00" if date_start else None,
"planperiod_end": f"{date_end}T02:00:00+02:00" if date_end else None,
"usageperiod_start": f"{date_start}T18:00:00+02:00" if date_start else None,
"usageperiod_end": f"{date_end}T23:59:00+02:00" if date_end else None,
"contact_name": data.get("contact_company") or data.get("contact_name", ""),
"contact_person_first_name": first_name,
"contact_person_lastname": last_name,
"contact_person_email": data.get("contact_email", ""),
"contact_person_phone": data.get("contact_phone", ""),
"location_name": data.get("location", ""),
"location_mailing_street": street,
"location_mailing_number": house_number,
"location_mailing_postalcode": data.get("contact_postalcode", ""),
"location_mailing_city": data.get("contact_city") or data.get("location", ""),
"remark": data.get("message", ""),
}
@staticmethod
def build_equipment_payload(item: dict[str, Any]) -> dict[str, Any]:
"""Map a single rental request item to Rentman projectrequestequipment payload."""
return {
"name": item.get("equipment_name", ""),
"quantity": item.get("quantity", 1),
"quantity_total": item.get("quantity", 1),
"unit_price": item.get("unit_price", 0),
"linked_equipment": f"/equipment/{item.get('rentman_equipment_id', '')}" if item.get("rentman_equipment_id") else None,
}