""" app/api/scheduled.py -------------------- Mobile API endpoint for planned/recurring inspection assignments (phase36). GET /api/v1/scheduled-inspections Returns ACTIVE scheduled inspections the caller is responsible for. - inspector : only schedules where inspector_id == the caller - admin / director / project_manager : all active schedules Powers the "Scheduled" section on the iPad Dashboard and My Inspections lists. The iPad taps "Start", which opens the normal new-inspection flow with the facility + template preselected (client-side); the schedule lifecycle (fulfil / roll-forward) continues to be driven by the web app. A ScheduledInspection is a PLAN, not an inspection — see app/models/scheduled_inspection.py for the full lifecycle. """ import logging from flask import Blueprint, request, g from app.models.scheduled_inspection import ScheduledInspection from app.api.errors import api_ok, api_error from app.api.decorators import jwt_required from app.utils.scope import get_inspector_scope logger = logging.getLogger(__name__) bp = Blueprint('api_scheduled', __name__) _ALLOWED_ROLES = {'admin', 'director', 'inspector', 'project_manager', 'auditor'} def _scheduled_payload(s): """Serialise a ScheduledInspection to the dict returned in list responses.""" return { 'id': s.id, 'facility_id': s.facility_id, 'facility_name': s.facility.name if s.facility else None, 'template_id': s.template_id, 'template_name': s.template.name if s.template else None, 'inspector_id': s.inspector_id, 'frequency': s.frequency, 'frequency_label': s.frequency_label, # phase43 recurrence detail. `recurrence_label` is the display string # ("Weekly · Mon, Wed, Fri"); the raw fields let the iPad render its own. 'recurrence_label': s.recurrence_label, 'weekdays': s.weekday_list, 'month_mode': s.month_mode, 'day_of_month': s.day_of_month, 'nth_week': s.nth_week, 'nth_weekday': s.nth_weekday, 'next_due_date': s.next_due_date.isoformat() if s.next_due_date else None, 'is_overdue': s.is_overdue(), 'notes': s.notes or None, } # ── List Scheduled Inspections ──────────────────────────────────────────────── @bp.route('/scheduled-inspections', methods=['GET']) @jwt_required def list_scheduled(): """ Return active scheduled inspections for the authenticated user. Query parameters ---------------- limit int Default 100, max 200. offset int Default 0. Response 200 ------------ { "ok": true, "data": { "scheduled": [...], "total": 3, "limit": 100, "offset": 0 } } """ user = g.api_user if user.role not in _ALLOWED_ROLES: return api_error('Access denied', 403) limit = min(int(request.args.get('limit', 100)), 200) offset = max(int(request.args.get('offset', 0)), 0) query = ScheduledInspection.query.filter(ScheduledInspection.active.is_(True)) if user.role == 'inspector': # Inspectors only see schedules assigned directly to them. query = query.filter(ScheduledInspection.inspector_id == user.id) total = query.count() rows = ( query .order_by(ScheduledInspection.next_due_date.asc()) .offset(offset) .limit(limit) .all() ) payload = [_scheduled_payload(s) for s in rows] logger.info('API SCHEDULED | list | user=%s | count=%d | total=%d', user.username, len(payload), total) return api_ok({'scheduled': payload, 'total': total, 'limit': limit, 'offset': offset})