147 lines
6.1 KiB
Python
147 lines
6.1 KiB
Python
|
|
"""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,
|
||
|
|
}
|