177 lines
5.7 KiB
Python
177 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Smazani rezervace / poznamky v Medevio kalendari.
|
|
|
|
Defaultni chovani:
|
|
- jednorazova: smazat (cancel)
|
|
- opakujici se: smazat tuto a nasledujici (ThisAndFuture)
|
|
|
|
Pouziti jako modul:
|
|
from smaz_poznamku import smaz_jednorazovou, smaz_opakujici
|
|
|
|
smaz_jednorazovou("<reservation_uuid>")
|
|
smaz_opakujici("<recurring_id>", date="2026-05-30T14:40:00.000Z") # default ThisAndFuture
|
|
smaz_opakujici("<recurring_id>", date="...Z", update_type="All")
|
|
smaz_opakujici("<recurring_id>", date="...Z", update_type="Single")
|
|
|
|
CLI:
|
|
python smaz_poznamku.py --jednorazova <reservation_uuid>
|
|
python smaz_poznamku.py --opakujici <recurring_id> --datum 2026-05-30T14:40:00.000Z
|
|
python smaz_poznamku.py --opakujici <id> --datum ...Z --typ All
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import argparse
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
import requests
|
|
from dateutil import parser as dtparser, tz
|
|
|
|
try:
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
except AttributeError:
|
|
pass
|
|
|
|
GRAPHQL_URL = "https://api.medevio.cz/graphql"
|
|
CLINIC_SLUG = "mudr-buzalkova"
|
|
PRAGUE_TZ = tz.gettz("Europe/Prague")
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent
|
|
TOKEN_PATH = BASE_DIR / "token.txt"
|
|
DEBUG_DIR = BASE_DIR / "debug"
|
|
|
|
VALID_UPDATE_TYPES = ("Single", "ThisAndFuture", "All")
|
|
|
|
MUTATION_SINGLE = """mutation UpdateReservation_CancelReservationByDoctor(
|
|
$clinicSlug: String!, $reservationId: UUID!
|
|
) {
|
|
reservation: cancelReservationByDoctor(
|
|
clinicSlug: $clinicSlug, reservationId: $reservationId
|
|
) { id __typename }
|
|
}"""
|
|
|
|
MUTATION_RECURRING = """mutation UpdateReservation_CancelRecurringReservationByDoctor(
|
|
$input: RemoveRecurringReservationInput!
|
|
) {
|
|
success: removeDateFromRecurringReservation(input: $input)
|
|
}"""
|
|
|
|
|
|
def _load_token() -> str:
|
|
token = TOKEN_PATH.read_text(encoding="utf-8").strip()
|
|
if token.startswith("Bearer "):
|
|
token = token.split(" ", 1)[1]
|
|
return token
|
|
|
|
|
|
def _headers() -> dict:
|
|
return {
|
|
"content-type": "application/json",
|
|
"authorization": f"Bearer {_load_token()}",
|
|
"origin": "https://my.medevio.cz",
|
|
"referer": "https://my.medevio.cz/",
|
|
}
|
|
|
|
|
|
def _to_utc_iso(value) -> str:
|
|
"""Prijme str/datetime/date a vrati '....000Z'."""
|
|
if isinstance(value, str):
|
|
if value.endswith("Z") and "T" in value:
|
|
return value
|
|
dt = dtparser.parse(value)
|
|
elif isinstance(value, datetime):
|
|
dt = value
|
|
else:
|
|
raise TypeError(f"Nelze prevest na datetime: {value!r}")
|
|
if dt.tzinfo is None:
|
|
dt = dt.replace(tzinfo=PRAGUE_TZ)
|
|
return dt.astimezone(tz.UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
|
|
|
|
|
def _post(payload: dict, label: str, save_debug: bool = True) -> dict:
|
|
r = requests.post(GRAPHQL_URL, headers=_headers(), data=json.dumps(payload), timeout=30)
|
|
r.raise_for_status()
|
|
data = r.json()
|
|
|
|
if save_debug:
|
|
DEBUG_DIR.mkdir(exist_ok=True)
|
|
p = DEBUG_DIR / f"smaz_{label}_{datetime.now():%Y%m%d_%H%M%S}.json"
|
|
p.write_text(
|
|
json.dumps({"request": payload["variables"], "response": data}, ensure_ascii=False, indent=2),
|
|
encoding="utf-8",
|
|
)
|
|
print(f"[debug] {p}")
|
|
|
|
if "errors" in data:
|
|
raise RuntimeError(f"GraphQL error: {data['errors']}")
|
|
return data["data"]
|
|
|
|
|
|
def smaz_jednorazovou(reservation_id: str, clinic_slug: str = CLINIC_SLUG) -> dict:
|
|
"""Zrusi (cancel) jednorazovou rezervaci. Vraci {'id': ..., '__typename': ...}."""
|
|
payload = {
|
|
"operationName": "UpdateReservation_CancelReservationByDoctor",
|
|
"variables": {"clinicSlug": clinic_slug, "reservationId": reservation_id},
|
|
"query": MUTATION_SINGLE,
|
|
}
|
|
result = _post(payload, "single")["reservation"]
|
|
print(f"OK zruseno: {result['id']}")
|
|
return result
|
|
|
|
|
|
def smaz_opakujici(
|
|
recurring_id: str,
|
|
date,
|
|
update_type: str = "ThisAndFuture",
|
|
clinic_slug: str = CLINIC_SLUG,
|
|
) -> bool:
|
|
"""Smaze opakujici se rezervaci.
|
|
|
|
Args:
|
|
recurring_id: UUID `recurringReservation.id`
|
|
date: datum konkretni instance (ISO str s Z, nebo datetime, nebo 'YYYY-MM-DD HH:MM' v lokal cas)
|
|
update_type: 'Single' | 'ThisAndFuture' (default) | 'All'
|
|
"""
|
|
if update_type not in VALID_UPDATE_TYPES:
|
|
raise ValueError(f"update_type musi byt {VALID_UPDATE_TYPES}, dostal '{update_type}'")
|
|
payload = {
|
|
"operationName": "UpdateReservation_CancelRecurringReservationByDoctor",
|
|
"variables": {
|
|
"input": {
|
|
"clinicSlug": clinic_slug,
|
|
"recurringReservationId": recurring_id,
|
|
"date": _to_utc_iso(date),
|
|
"updateType": update_type,
|
|
}
|
|
},
|
|
"query": MUTATION_RECURRING,
|
|
}
|
|
success = _post(payload, f"recurring_{update_type}")["success"]
|
|
print(f"OK smazano ({update_type}): {recurring_id} @ {payload['variables']['input']['date']} -> success={success}")
|
|
return success
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description="Smazani rezervace v Medevio kalendari.")
|
|
g = ap.add_mutually_exclusive_group(required=True)
|
|
g.add_argument("--jednorazova", help="UUID jednorazove rezervace ke zruseni")
|
|
g.add_argument("--opakujici", help="UUID opakujiciho se pravidla")
|
|
ap.add_argument("--datum", help="Datum instance opakujici (ISO Z nebo 'YYYY-MM-DD HH:MM')")
|
|
ap.add_argument("--typ", choices=VALID_UPDATE_TYPES, default="ThisAndFuture",
|
|
help="Pro --opakujici. Default ThisAndFuture.")
|
|
args = ap.parse_args()
|
|
|
|
if args.jednorazova:
|
|
smaz_jednorazovou(args.jednorazova)
|
|
else:
|
|
if not args.datum:
|
|
ap.error("--opakujici vyzaduje --datum")
|
|
smaz_opakujici(args.opakujici, args.datum, update_type=args.typ)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|