feat(events): reliable holiday skipping for recurrences + UI badge; clean logs
Backend: generate EventException on create/update when skip_holidays or recurrence changes; emit RecurrenceException (EXDATE) with exact occurrence start time (UTC) API: return master events with RecurrenceRule + RecurrenceException Frontend: map RecurrenceException → recurrenceException; ensure SkipHolidays instances never render on holidays; place TentTree icon (black) next to main event icon via template Docs: update README and Copilot instructions for recurrence/holiday behavior Cleanup: remove dataSource and debug console logs
This commit is contained in:
113
server/routes/event_exceptions.py
Normal file
113
server/routes/event_exceptions.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from server.database import Session
|
||||
from models.models import EventException, Event
|
||||
from datetime import datetime, date
|
||||
|
||||
event_exceptions_bp = Blueprint("event_exceptions", __name__, url_prefix="/api/event_exceptions")
|
||||
|
||||
|
||||
@event_exceptions_bp.route("", methods=["POST"])
|
||||
def create_exception():
|
||||
data = request.json
|
||||
session = Session()
|
||||
# required: event_id, exception_date
|
||||
required = ["event_id", "exception_date"]
|
||||
for f in required:
|
||||
if f not in data:
|
||||
return jsonify({"error": f"Missing field: {f}"}), 400
|
||||
|
||||
# Validate event exists
|
||||
event = session.query(Event).filter_by(id=data["event_id"]).first()
|
||||
if not event:
|
||||
session.close()
|
||||
return jsonify({"error": "Event not found"}), 404
|
||||
|
||||
exc_date = datetime.fromisoformat(data["exception_date"]).date() if isinstance(data["exception_date"], str) else data["exception_date"]
|
||||
# Check if an exception for this event and date already exists
|
||||
existing_exc = session.query(EventException).filter_by(event_id=event.id, exception_date=exc_date).first()
|
||||
if existing_exc:
|
||||
# Optionally, update the existing exception fields if needed
|
||||
existing_exc.is_skipped = bool(data.get("is_skipped", existing_exc.is_skipped))
|
||||
existing_exc.override_title = data.get("override_title", existing_exc.override_title)
|
||||
existing_exc.override_description = data.get("override_description", existing_exc.override_description)
|
||||
existing_exc.override_start = datetime.fromisoformat(data["override_start"]) if data.get("override_start") else existing_exc.override_start
|
||||
existing_exc.override_end = datetime.fromisoformat(data["override_end"]) if data.get("override_end") else existing_exc.override_end
|
||||
session.commit()
|
||||
return jsonify({"success": True, "id": existing_exc.id, "updated": True})
|
||||
# Otherwise, create a new exception
|
||||
exc = EventException(
|
||||
event_id=event.id,
|
||||
exception_date=exc_date,
|
||||
is_skipped=bool(data.get("is_skipped", False)),
|
||||
override_title=data.get("override_title"),
|
||||
override_description=data.get("override_description"),
|
||||
override_start=(datetime.fromisoformat(data["override_start"]) if data.get("override_start") else None),
|
||||
override_end=(datetime.fromisoformat(data["override_end"]) if data.get("override_end") else None),
|
||||
)
|
||||
session.add(exc)
|
||||
session.commit()
|
||||
return jsonify({"success": True, "id": exc.id})
|
||||
|
||||
|
||||
@event_exceptions_bp.route("/<exc_id>", methods=["PUT"])
|
||||
def update_exception(exc_id):
|
||||
data = request.json
|
||||
session = Session()
|
||||
exc = session.query(EventException).filter_by(id=exc_id).first()
|
||||
if not exc:
|
||||
session.close()
|
||||
return jsonify({"error": "Exception not found"}), 404
|
||||
|
||||
if "exception_date" in data:
|
||||
exc.exception_date = datetime.fromisoformat(data["exception_date"]).date()
|
||||
if "is_skipped" in data:
|
||||
exc.is_skipped = bool(data["is_skipped"])
|
||||
if "override_title" in data:
|
||||
exc.override_title = data.get("override_title")
|
||||
if "override_description" in data:
|
||||
exc.override_description = data.get("override_description")
|
||||
if "override_start" in data:
|
||||
exc.override_start = datetime.fromisoformat(data["override_start"]) if data.get("override_start") else None
|
||||
if "override_end" in data:
|
||||
exc.override_end = datetime.fromisoformat(data["override_end"]) if data.get("override_end") else None
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
return jsonify({"success": True})
|
||||
|
||||
|
||||
@event_exceptions_bp.route("/<exc_id>", methods=["DELETE"])
|
||||
def delete_exception(exc_id):
|
||||
session = Session()
|
||||
exc = session.query(EventException).filter_by(id=exc_id).first()
|
||||
if not exc:
|
||||
session.close()
|
||||
return jsonify({"error": "Exception not found"}), 404
|
||||
session.delete(exc)
|
||||
session.commit()
|
||||
session.close()
|
||||
return jsonify({"success": True})
|
||||
|
||||
|
||||
@event_exceptions_bp.route("", methods=["GET"])
|
||||
def list_exceptions():
|
||||
session = Session()
|
||||
event_id = request.args.get("event_id")
|
||||
q = session.query(EventException)
|
||||
if event_id:
|
||||
q = q.filter(EventException.event_id == int(event_id))
|
||||
rows = q.all()
|
||||
out = []
|
||||
for r in rows:
|
||||
out.append({
|
||||
"id": r.id,
|
||||
"event_id": r.event_id,
|
||||
"exception_date": r.exception_date.isoformat(),
|
||||
"is_skipped": r.is_skipped,
|
||||
"override_title": r.override_title,
|
||||
"override_description": r.override_description,
|
||||
"override_start": r.override_start.isoformat() if r.override_start else None,
|
||||
"override_end": r.override_end.isoformat() if r.override_end else None,
|
||||
})
|
||||
session.close()
|
||||
return jsonify(out)
|
||||
Reference in New Issue
Block a user