feat(backend): add ability to schedule member in cli
This commit is contained in:
@@ -5,7 +5,7 @@ CLI command modules.
|
||||
from .members import cmd_members_list, cmd_members_show, setup_members_parser
|
||||
from .schedules import (
|
||||
cmd_schedules_list, cmd_schedules_show, cmd_schedules_accept,
|
||||
cmd_schedules_decline, setup_schedules_parser
|
||||
cmd_schedules_decline, cmd_schedules_schedule, setup_schedules_parser
|
||||
)
|
||||
from .services import cmd_services_list, setup_services_parser
|
||||
|
||||
@@ -14,7 +14,7 @@ __all__ = [
|
||||
"cmd_members_list", "cmd_members_show", "setup_members_parser",
|
||||
# Schedule commands
|
||||
"cmd_schedules_list", "cmd_schedules_show", "cmd_schedules_accept",
|
||||
"cmd_schedules_decline", "setup_schedules_parser",
|
||||
"cmd_schedules_decline", "cmd_schedules_schedule", "setup_schedules_parser",
|
||||
# Service commands
|
||||
"cmd_services_list", "setup_services_parser",
|
||||
]
|
||||
@@ -357,6 +357,178 @@ def cmd_schedules_decline(cli: "NimbusFlowCLI", args) -> None:
|
||||
print(f" Reason: {decline_reason}")
|
||||
|
||||
|
||||
def cmd_schedules_schedule(cli: "NimbusFlowCLI", args) -> None:
|
||||
"""Schedule next member for a service with preview and confirmation."""
|
||||
|
||||
# Determine if we're using service ID or date-based selection
|
||||
service = None
|
||||
|
||||
if hasattr(args, 'date') and args.date:
|
||||
# Date-based selection
|
||||
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("-" * 50)
|
||||
for i, svc in enumerate(services_on_date, 1):
|
||||
type_name = service_type_map.get(svc.ServiceTypeId, "Unknown")
|
||||
print(f"{i}. {type_name} (Service ID: {svc.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
|
||||
|
||||
service = services_on_date[service_index]
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\n🛑 Operation cancelled")
|
||||
return
|
||||
|
||||
elif hasattr(args, 'service_id') and args.service_id:
|
||||
# Service ID based selection
|
||||
print(f"Scheduling for service ID {args.service_id}...")
|
||||
service = cli.service_repo.get_by_id(args.service_id)
|
||||
if not service:
|
||||
print(f"❌ Service ID {args.service_id} not found.")
|
||||
return
|
||||
|
||||
else:
|
||||
print("❌ Either --date or service_id must be provided")
|
||||
return
|
||||
|
||||
# Get service type name
|
||||
service_type = cli.service_type_repo.get_by_id(service.ServiceTypeId)
|
||||
service_type_name = service_type.TypeName if service_type else "Unknown"
|
||||
|
||||
print(f"\n📅 Selected Service: {service_type_name} on {service.ServiceDate}")
|
||||
|
||||
# Get classification constraints if not provided
|
||||
classification_ids = []
|
||||
if args.classifications:
|
||||
# Convert classification names to IDs
|
||||
all_classifications = cli.classification_repo.list_all()
|
||||
classification_map = {c.ClassificationName.lower(): c.ClassificationId for c in all_classifications}
|
||||
|
||||
for class_name in args.classifications:
|
||||
class_id = classification_map.get(class_name.lower())
|
||||
if class_id:
|
||||
classification_ids.append(class_id)
|
||||
else:
|
||||
print(f"❌ Unknown classification: {class_name}")
|
||||
return
|
||||
else:
|
||||
# If no classifications specified, ask user to select
|
||||
all_classifications = cli.classification_repo.list_all()
|
||||
print("\n🎵 Available classifications:")
|
||||
for i, classification in enumerate(all_classifications, 1):
|
||||
print(f" {i}. {classification.ClassificationName}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input(f"\n🎯 Select classification(s) (1-{len(all_classifications)}, comma-separated): ").strip()
|
||||
if not choice:
|
||||
continue
|
||||
|
||||
selections = [int(x.strip()) for x in choice.split(',')]
|
||||
if all(1 <= sel <= len(all_classifications) for sel in selections):
|
||||
classification_ids = [all_classifications[sel-1].ClassificationId for sel in selections]
|
||||
break
|
||||
else:
|
||||
print(f"❌ Please enter numbers between 1 and {len(all_classifications)}")
|
||||
except ValueError:
|
||||
print("❌ Please enter valid numbers")
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\n🛑 Operation cancelled")
|
||||
return
|
||||
|
||||
# Get selected classification names for display
|
||||
selected_classifications = [c.ClassificationName for c in cli.classification_repo.list_all()
|
||||
if c.ClassificationId in classification_ids]
|
||||
|
||||
print(f"\n🔍 Looking for eligible members in: {', '.join(selected_classifications)}")
|
||||
|
||||
excluded_members = set()
|
||||
|
||||
while True:
|
||||
# Preview who would be scheduled (excluding any we've already shown)
|
||||
preview_result = cli.scheduling_service.preview_next_member(
|
||||
classification_ids=classification_ids,
|
||||
service_id=service.ServiceId,
|
||||
only_active=True,
|
||||
exclude_member_ids=excluded_members,
|
||||
)
|
||||
|
||||
if not preview_result:
|
||||
if excluded_members:
|
||||
print("❌ No more eligible members found for this service.")
|
||||
else:
|
||||
print("❌ No eligible members found for this service.")
|
||||
return
|
||||
|
||||
member_id, first_name, last_name = preview_result
|
||||
|
||||
# Show preview
|
||||
print(f"\n✨ Next available member:")
|
||||
print(f" 👤 {first_name} {last_name} (ID: {member_id})")
|
||||
|
||||
# Confirm scheduling
|
||||
try:
|
||||
confirm = input(f"\n🤔 Schedule {first_name} {last_name} for this service? (y/N/q to quit): ").strip().lower()
|
||||
|
||||
if confirm in ['y', 'yes']:
|
||||
# Actually create the schedule
|
||||
result = cli.scheduling_service.schedule_next_member(
|
||||
classification_ids=classification_ids,
|
||||
service_id=service.ServiceId,
|
||||
only_active=True,
|
||||
exclude_member_ids=excluded_members,
|
||||
)
|
||||
|
||||
if result:
|
||||
scheduled_member_id, scheduled_first, scheduled_last, schedule_id = result
|
||||
print(f"\n✅ Successfully scheduled {scheduled_first} {scheduled_last}!")
|
||||
print(f" 📋 Schedule ID: {schedule_id}")
|
||||
print(f" 📧 Status: Pending (awaiting member response)")
|
||||
else:
|
||||
print("❌ Failed to create schedule. The member may no longer be eligible.")
|
||||
return
|
||||
|
||||
elif confirm in ['q', 'quit']:
|
||||
print("🛑 Scheduling cancelled")
|
||||
return
|
||||
|
||||
else:
|
||||
# User declined this member - add to exclusion list and continue
|
||||
excluded_members.add(member_id)
|
||||
print(f"⏭️ Skipping {first_name} {last_name}, looking for next member...")
|
||||
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\n🛑 Operation cancelled")
|
||||
return
|
||||
|
||||
|
||||
def setup_schedules_parser(subparsers) -> None:
|
||||
"""Set up schedule-related command parsers."""
|
||||
# Schedules commands
|
||||
@@ -381,4 +553,10 @@ def setup_schedules_parser(subparsers) -> None:
|
||||
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")
|
||||
schedules_decline_parser.add_argument("--reason", type=str, help="Reason for declining")
|
||||
|
||||
# schedules schedule
|
||||
schedules_schedule_parser = schedules_subparsers.add_parser("schedule", help="Schedule next member for a service (cycles through eligible members)")
|
||||
schedules_schedule_parser.add_argument("service_id", type=int, nargs="?", help="Service ID to schedule for (optional if using --date)")
|
||||
schedules_schedule_parser.add_argument("--date", type=str, help="Interactive mode: select service by date (YYYY-MM-DD)")
|
||||
schedules_schedule_parser.add_argument("--classifications", nargs="*", help="Classification names to filter by (e.g., Soprano Alto)")
|
||||
Reference in New Issue
Block a user