""" Schedule-related CLI commands. """ import argparse from datetime import date from typing import TYPE_CHECKING if TYPE_CHECKING: from backend.cli.base import NimbusFlowCLI from backend.models.enums import ScheduleStatus from backend.cli.utils import format_schedule_row def cmd_schedules_list(cli: "NimbusFlowCLI", args) -> None: """List schedules with optional filters.""" print("Listing schedules...") schedules = cli.schedule_repo.list_all() # Apply filters if args.service_id: schedules = [s for s in schedules if s.ServiceId == args.service_id] if args.status: try: status_enum = ScheduleStatus.from_raw(args.status.lower()) schedules = [s for s in schedules if s.Status == status_enum.value] except ValueError: print(f"āŒ Invalid status '{args.status}'. Valid options: pending, accepted, declined") return if not schedules: print("No schedules found.") return # Get member and service info for display member_map = {m.MemberId: f"{m.FirstName} {m.LastName}" for m in cli.member_repo.list_all()} service_map = {} service_type_map = {st.ServiceTypeId: st.TypeName for st in cli.service_type_repo.list_all()} for service in cli.service_repo.list_all(): type_name = service_type_map.get(service.ServiceTypeId, "Unknown") service_map[service.ServiceId] = f"{type_name} {service.ServiceDate}" # Print header print(f"\n{'ID':<3} | {'Status':<10} | {'Member':<20} | {'Service':<15} | {'Scheduled'}") print("-" * 80) # Print schedules for schedule in schedules: member_name = member_map.get(schedule.MemberId, f"ID:{schedule.MemberId}") service_info = service_map.get(schedule.ServiceId, f"ID:{schedule.ServiceId}") print(format_schedule_row(schedule, member_name, service_info)) print(f"\nTotal: {len(schedules)} schedules") def cmd_schedules_show(cli: "NimbusFlowCLI", args) -> None: """Show detailed information about a specific schedule.""" schedule = cli.schedule_repo.get_by_id(args.schedule_id) if not schedule: print(f"āŒ Schedule with ID {args.schedule_id} not found") return # Get related information member = cli.member_repo.get_by_id(schedule.MemberId) service = cli.service_repo.get_by_id(schedule.ServiceId) service_type = None if service: service_type = cli.service_type_repo.get_by_id(service.ServiceTypeId) print(f"\nšŸ“… Schedule Details (ID: {schedule.ScheduleId})") print("-" * 50) print(f"Member: {member.FirstName} {member.LastName} (ID: {member.MemberId})" if member else f"Member ID: {schedule.MemberId}") print(f"Service: {service_type.TypeName if service_type else 'Unknown'} on {service.ServiceDate if service else 'Unknown'}") print(f"Status: {schedule.Status.upper()}") print(f"Scheduled At: {schedule.ScheduledAt}") print(f"Accepted At: {schedule.AcceptedAt or 'N/A'}") print(f"Declined At: {schedule.DeclinedAt or 'N/A'}") print(f"Expires At: {schedule.ExpiresAt or 'N/A'}") if schedule.DeclineReason: print(f"Decline Reason: {schedule.DeclineReason}") def cmd_schedules_accept(cli: "NimbusFlowCLI", args) -> None: """Accept a scheduled position.""" # Interactive mode with date parameter if hasattr(args, 'date') and args.date: try: target_date = date.fromisoformat(args.date) except ValueError: print(f"āŒ Invalid date format '{args.date}'. Use YYYY-MM-DD") return # Find services for the specified date all_services = cli.service_repo.list_all() services_on_date = [s for s in all_services if s.ServiceDate == target_date] if not services_on_date: print(f"āŒ No services found for {args.date}") return # Get service types for display service_type_map = {st.ServiceTypeId: st.TypeName for st in cli.service_type_repo.list_all()} # Show available services for the date print(f"\nšŸ“… Services available on {args.date}:") print("-" * 40) for i, service in enumerate(services_on_date, 1): type_name = service_type_map.get(service.ServiceTypeId, "Unknown") print(f"{i}. {type_name} (Service ID: {service.ServiceId})") # Let user select service try: choice = input(f"\nSelect service (1-{len(services_on_date)}): ").strip() if not choice or not choice.isdigit(): print("āŒ Invalid selection") return service_index = int(choice) - 1 if service_index < 0 or service_index >= len(services_on_date): print("āŒ Invalid selection") return selected_service = services_on_date[service_index] except (KeyboardInterrupt, EOFError): print("\nšŸ›‘ Cancelled") return # Find pending schedules for this service all_schedules = cli.schedule_repo.list_all() pending_schedules = [ s for s in all_schedules if s.ServiceId == selected_service.ServiceId and s.Status == ScheduleStatus.PENDING.value ] if not pending_schedules: service_type_name = service_type_map.get(selected_service.ServiceTypeId, "Unknown") print(f"āŒ No pending schedules found for {service_type_name} on {args.date}") return # Get member info for display member_map = {m.MemberId: m for m in cli.member_repo.list_all()} # Show available members to accept print(f"\nšŸ‘„ Members scheduled for {service_type_map.get(selected_service.ServiceTypeId, 'Unknown')} on {args.date}:") print("-" * 60) for i, schedule in enumerate(pending_schedules, 1): member = member_map.get(schedule.MemberId) if member: print(f"{i}. {member.FirstName} {member.LastName} (Schedule ID: {schedule.ScheduleId})") else: print(f"{i}. Unknown Member (ID: {schedule.MemberId}) (Schedule ID: {schedule.ScheduleId})") # Let user select member try: choice = input(f"\nSelect member to accept (1-{len(pending_schedules)}): ").strip() if not choice or not choice.isdigit(): print("āŒ Invalid selection") return member_index = int(choice) - 1 if member_index < 0 or member_index >= len(pending_schedules): print("āŒ Invalid selection") return selected_schedule = pending_schedules[member_index] except (KeyboardInterrupt, EOFError): print("\nšŸ›‘ Cancelled") return # Accept the selected schedule schedule_to_accept = selected_schedule # Direct mode with schedule ID elif hasattr(args, 'schedule_id') and args.schedule_id: schedule_to_accept = cli.schedule_repo.get_by_id(args.schedule_id) if not schedule_to_accept: print(f"āŒ Schedule with ID {args.schedule_id} not found") return else: print("āŒ Either --date or schedule_id must be provided") return # Common validation and acceptance logic if schedule_to_accept.Status == ScheduleStatus.ACCEPTED.value: print(f"āš ļø Schedule {schedule_to_accept.ScheduleId} is already accepted") return if schedule_to_accept.Status == ScheduleStatus.DECLINED.value: print(f"āš ļø Schedule {schedule_to_accept.ScheduleId} was previously declined") return # Get member and service info for display member = cli.member_repo.get_by_id(schedule_to_accept.MemberId) service = cli.service_repo.get_by_id(schedule_to_accept.ServiceId) service_type = None if service: service_type = cli.service_type_repo.get_by_id(service.ServiceTypeId) # Mark the schedule as accepted cli.schedule_repo.mark_accepted(schedule_to_accept.ScheduleId) # Update member's acceptance timestamp cli.member_repo.set_last_accepted(schedule_to_accept.MemberId) print(f"āœ… Schedule {schedule_to_accept.ScheduleId} accepted successfully!") if member and service and service_type: print(f" Member: {member.FirstName} {member.LastName}") print(f" Service: {service_type.TypeName} on {service.ServiceDate}") def cmd_schedules_decline(cli: "NimbusFlowCLI", args) -> None: """Decline a scheduled position.""" # Interactive mode with date parameter if hasattr(args, 'date') and args.date: try: target_date = date.fromisoformat(args.date) except ValueError: print(f"āŒ Invalid date format '{args.date}'. Use YYYY-MM-DD") return # Find services for the specified date all_services = cli.service_repo.list_all() services_on_date = [s for s in all_services if s.ServiceDate == target_date] if not services_on_date: print(f"āŒ No services found for {args.date}") return # Get service types for display service_type_map = {st.ServiceTypeId: st.TypeName for st in cli.service_type_repo.list_all()} # Show available services for the date print(f"\nšŸ“… Services available on {args.date}:") print("-" * 40) for i, service in enumerate(services_on_date, 1): type_name = service_type_map.get(service.ServiceTypeId, "Unknown") print(f"{i}. {type_name} (Service ID: {service.ServiceId})") # Let user select service try: choice = input(f"\nSelect service (1-{len(services_on_date)}): ").strip() if not choice or not choice.isdigit(): print("āŒ Invalid selection") return service_index = int(choice) - 1 if service_index < 0 or service_index >= len(services_on_date): print("āŒ Invalid selection") return selected_service = services_on_date[service_index] except (KeyboardInterrupt, EOFError): print("\nšŸ›‘ Cancelled") return # Find pending schedules for this service all_schedules = cli.schedule_repo.list_all() pending_schedules = [ s for s in all_schedules if s.ServiceId == selected_service.ServiceId and s.Status == ScheduleStatus.PENDING.value ] if not pending_schedules: service_type_name = service_type_map.get(selected_service.ServiceTypeId, "Unknown") print(f"āŒ No pending schedules found for {service_type_name} on {args.date}") return # Get member info for display member_map = {m.MemberId: m for m in cli.member_repo.list_all()} # Show available members to decline print(f"\nšŸ‘„ Members scheduled for {service_type_map.get(selected_service.ServiceTypeId, 'Unknown')} on {args.date}:") print("-" * 60) for i, schedule in enumerate(pending_schedules, 1): member = member_map.get(schedule.MemberId) if member: print(f"{i}. {member.FirstName} {member.LastName} (Schedule ID: {schedule.ScheduleId})") else: print(f"{i}. Unknown Member (ID: {schedule.MemberId}) (Schedule ID: {schedule.ScheduleId})") # Let user select member try: choice = input(f"\nSelect member to decline (1-{len(pending_schedules)}): ").strip() if not choice or not choice.isdigit(): print("āŒ Invalid selection") return member_index = int(choice) - 1 if member_index < 0 or member_index >= len(pending_schedules): print("āŒ Invalid selection") return selected_schedule = pending_schedules[member_index] except (KeyboardInterrupt, EOFError): print("\nšŸ›‘ Cancelled") return # Get decline reason if not provided decline_reason = args.reason if hasattr(args, 'reason') and args.reason else None if not decline_reason: try: decline_reason = input("\nEnter decline reason (optional, press Enter to skip): ").strip() if not decline_reason: decline_reason = None except (KeyboardInterrupt, EOFError): print("\nšŸ›‘ Cancelled") return # Decline the selected schedule schedule_to_decline = selected_schedule # Direct mode with schedule ID elif hasattr(args, 'schedule_id') and args.schedule_id: schedule_to_decline = cli.schedule_repo.get_by_id(args.schedule_id) if not schedule_to_decline: print(f"āŒ Schedule with ID {args.schedule_id} not found") return decline_reason = args.reason if hasattr(args, 'reason') else None else: print("āŒ Either --date or schedule_id must be provided") return # Common validation and decline logic if schedule_to_decline.Status == ScheduleStatus.DECLINED.value: print(f"āš ļø Schedule {schedule_to_decline.ScheduleId} is already declined") return if schedule_to_decline.Status == ScheduleStatus.ACCEPTED.value: print(f"āš ļø Schedule {schedule_to_decline.ScheduleId} was previously accepted") return # Get member and service info for display member = cli.member_repo.get_by_id(schedule_to_decline.MemberId) service = cli.service_repo.get_by_id(schedule_to_decline.ServiceId) service_type = None if service: service_type = cli.service_type_repo.get_by_id(service.ServiceTypeId) # Mark the schedule as declined cli.schedule_repo.mark_declined(schedule_to_decline.ScheduleId, decline_reason=decline_reason) # Update member's decline timestamp (using service date) if service: cli.member_repo.set_last_declined(schedule_to_decline.MemberId, str(service.ServiceDate)) print(f"āŒ Schedule {schedule_to_decline.ScheduleId} declined successfully!") if member and service and service_type: print(f" Member: {member.FirstName} {member.LastName}") print(f" Service: {service_type.TypeName} on {service.ServiceDate}") if decline_reason: print(f" Reason: {decline_reason}") def setup_schedules_parser(subparsers) -> None: """Set up schedule-related command parsers.""" # Schedules commands schedules_parser = subparsers.add_parser("schedules", help="Manage schedules") schedules_subparsers = schedules_parser.add_subparsers(dest="schedules_action", help="Schedule actions") # schedules list schedules_list_parser = schedules_subparsers.add_parser("list", help="List schedules") schedules_list_parser.add_argument("--service-id", type=int, help="Filter by service ID") schedules_list_parser.add_argument("--status", type=str, help="Filter by status (pending/accepted/declined)") # schedules show schedules_show_parser = schedules_subparsers.add_parser("show", help="Show schedule details") schedules_show_parser.add_argument("schedule_id", type=int, help="Schedule ID to show") # schedules accept schedules_accept_parser = schedules_subparsers.add_parser("accept", help="Accept a scheduled position") schedules_accept_parser.add_argument("schedule_id", type=int, nargs="?", help="Schedule ID to accept (optional if using --date)") schedules_accept_parser.add_argument("--date", type=str, help="Interactive mode: select service and member by date (YYYY-MM-DD)") # schedules decline schedules_decline_parser = schedules_subparsers.add_parser("decline", help="Decline a scheduled position") schedules_decline_parser.add_argument("schedule_id", type=int, nargs="?", help="Schedule ID to decline (optional if using --date)") schedules_decline_parser.add_argument("--date", type=str, help="Interactive mode: select service and member by date (YYYY-MM-DD)") schedules_decline_parser.add_argument("--reason", type=str, help="Reason for declining")