Update app.py
Browse files
app.py
CHANGED
@@ -46,39 +46,57 @@ class MindbodyClient:
|
|
46 |
resp.raise_for_status()
|
47 |
return resp.json()
|
48 |
|
49 |
-
|
50 |
-
|
51 |
-
"""Return **types of training** offered at a location."""
|
52 |
-
params = {"LocationIds": location_id}
|
53 |
return self._get("/class/classdescriptions", params)
|
54 |
|
55 |
-
def get_classes(
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
return self._get("/class/classes", params)
|
63 |
|
64 |
-
def book_class(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
"""Book a client into a class *after* verifying availability."""
|
66 |
-
# 1)
|
67 |
-
info = self.
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
cls = info["Classes"][0]
|
72 |
if not cls.get("IsAvailable", False):
|
73 |
-
raise ValueError("Class is not
|
74 |
if cls.get("TotalBooked", 0) >= cls.get("MaxCapacity", 0):
|
75 |
raise ValueError("Class is already full")
|
76 |
|
77 |
-
# 2) book
|
78 |
payload = {
|
79 |
"ClientId": client_id,
|
80 |
"ClassId": class_id,
|
81 |
"CrossRegionalBooking": cross_regional,
|
|
|
|
|
82 |
}
|
83 |
return self._post("/class/addclienttoclass", payload)
|
84 |
|
@@ -129,43 +147,53 @@ def get_training_types(location_id: str | int):
|
|
129 |
return f"❌ Error: {exc}"
|
130 |
|
131 |
|
132 |
-
def get_schedule(location_id: str | int, days:
|
133 |
"""Return upcoming classes in a human-friendly Markdown list."""
|
134 |
if not mb_client:
|
135 |
return "❌ Authenticate first."
|
136 |
|
137 |
try:
|
138 |
-
|
139 |
-
|
140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
classes = res.get("Classes", [])
|
142 |
if not classes:
|
143 |
return "⚠️ No classes in the selected window."
|
144 |
|
145 |
-
|
146 |
for c in classes:
|
147 |
-
|
148 |
-
name = c.get("ClassDescription", {}).get("Name"
|
149 |
-
ok = "✅" if c
|
150 |
-
cid = c
|
151 |
-
|
152 |
|
153 |
-
return "📅 Schedule\n" + "\n".join(
|
154 |
except Exception as exc:
|
155 |
logger.exception("Error while fetching schedule")
|
156 |
return f"❌ Error: {exc}"
|
157 |
|
158 |
|
159 |
def book_class_gr(class_id: int, client_id: str, cross_regional: bool):
|
160 |
-
"""Book a class through the UI."""
|
161 |
if not mb_client:
|
162 |
return "❌ Authenticate first."
|
163 |
|
164 |
try:
|
165 |
-
res = mb_client.book_class(
|
|
|
|
|
|
|
|
|
166 |
visit = res.get("Visit", {})
|
167 |
status = visit.get("AppointmentStatus", "OK")
|
168 |
-
|
|
|
169 |
except Exception as exc:
|
170 |
logger.exception("Booking failed")
|
171 |
return f"❌ Could not book: {exc}"
|
|
|
46 |
resp.raise_for_status()
|
47 |
return resp.json()
|
48 |
|
49 |
+
def get_class_descriptions(self, location_id: int | str):
|
50 |
+
params = {"locationIds": location_id}
|
|
|
|
|
51 |
return self._get("/class/classdescriptions", params)
|
52 |
|
53 |
+
def get_classes(
|
54 |
+
self,
|
55 |
+
*,
|
56 |
+
location_id: int | str | None = None,
|
57 |
+
start_iso: str | None = None,
|
58 |
+
end_iso: str | None = None,
|
59 |
+
class_ids: list[int] | None = None,
|
60 |
+
):
|
61 |
+
"""Return scheduled classes. Accepts *either* a location or explicit classIds."""
|
62 |
+
params: dict[str, str | list[int]] = {}
|
63 |
+
if location_id is not None:
|
64 |
+
params["locationIds"] = location_id
|
65 |
+
if class_ids:
|
66 |
+
params["classIds"] = class_ids
|
67 |
+
if start_iso:
|
68 |
+
params["startDateTime"] = start_iso
|
69 |
+
if end_iso:
|
70 |
+
params["endDateTime"] = end_iso
|
71 |
return self._get("/class/classes", params)
|
72 |
|
73 |
+
def book_class(
|
74 |
+
self,
|
75 |
+
*,
|
76 |
+
class_id: int,
|
77 |
+
client_id: str,
|
78 |
+
cross_regional: bool = False,
|
79 |
+
require_payment: bool = False,
|
80 |
+
send_email: bool = False,
|
81 |
+
):
|
82 |
"""Book a client into a class *after* verifying availability."""
|
83 |
+
# 1) quick availability check
|
84 |
+
info = self.get_classes(class_ids=[class_id])
|
85 |
+
cls = (info.get("Classes") or [None])[0]
|
86 |
+
if not cls:
|
87 |
+
raise ValueError(f"Class {class_id} not found")
|
|
|
88 |
if not cls.get("IsAvailable", False):
|
89 |
+
raise ValueError("Class is not open for online booking")
|
90 |
if cls.get("TotalBooked", 0) >= cls.get("MaxCapacity", 0):
|
91 |
raise ValueError("Class is already full")
|
92 |
|
93 |
+
# 2) book it
|
94 |
payload = {
|
95 |
"ClientId": client_id,
|
96 |
"ClassId": class_id,
|
97 |
"CrossRegionalBooking": cross_regional,
|
98 |
+
"RequirePayment": require_payment,
|
99 |
+
"SendEmail": send_email,
|
100 |
}
|
101 |
return self._post("/class/addclienttoclass", payload)
|
102 |
|
|
|
147 |
return f"❌ Error: {exc}"
|
148 |
|
149 |
|
150 |
+
def get_schedule(location_id: str | int, days: float):
|
151 |
"""Return upcoming classes in a human-friendly Markdown list."""
|
152 |
if not mb_client:
|
153 |
return "❌ Authenticate first."
|
154 |
|
155 |
try:
|
156 |
+
now = datetime.utcnow()
|
157 |
+
start_iso = now.replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
|
158 |
+
end_iso = (now + timedelta(days=int(days))).replace(
|
159 |
+
hour=23, minute=59, second=59, microsecond=0
|
160 |
+
).isoformat()
|
161 |
+
|
162 |
+
res = mb_client.get_classes(
|
163 |
+
location_id=location_id, start_iso=start_iso, end_iso=end_iso
|
164 |
+
)
|
165 |
classes = res.get("Classes", [])
|
166 |
if not classes:
|
167 |
return "⚠️ No classes in the selected window."
|
168 |
|
169 |
+
lines: list[str] = []
|
170 |
for c in classes:
|
171 |
+
when = c["StartDateTime"][:16].replace("T", " ") # YYYY-MM-DD HH:MM
|
172 |
+
name = c.get("ClassDescription", {}).get("Name") or f"ID {c['ClassDescriptionId']}"
|
173 |
+
ok = "✅" if c["IsAvailable"] else "❌"
|
174 |
+
cid = c["Id"]
|
175 |
+
lines.append(f"{when} | {name} | {ok} | ClassId: {cid}")
|
176 |
|
177 |
+
return "### 📅 Schedule\n" + "\n".join(lines)
|
178 |
except Exception as exc:
|
179 |
logger.exception("Error while fetching schedule")
|
180 |
return f"❌ Error: {exc}"
|
181 |
|
182 |
|
183 |
def book_class_gr(class_id: int, client_id: str, cross_regional: bool):
|
|
|
184 |
if not mb_client:
|
185 |
return "❌ Authenticate first."
|
186 |
|
187 |
try:
|
188 |
+
res = mb_client.book_class(
|
189 |
+
class_id=class_id,
|
190 |
+
client_id=client_id,
|
191 |
+
cross_regional=cross_regional,
|
192 |
+
)
|
193 |
visit = res.get("Visit", {})
|
194 |
status = visit.get("AppointmentStatus", "OK")
|
195 |
+
readable = visit.get("StartDateTime", "")[:16].replace("T", " ")
|
196 |
+
return f"🎉 **Booked {readable} – status {status}**\n\n```json\n{res}\n```"
|
197 |
except Exception as exc:
|
198 |
logger.exception("Booking failed")
|
199 |
return f"❌ Could not book: {exc}"
|