## Backend Changes:
- Enhanced PluginTemplate.save() to auto-parse template variables from plugin code
- Updated PluginInstallationSerializer to expose template metadata (description, category, version, author, logo, template_variables)
- Fixed template variable parser to handle nested {{ }} braces in default values
- Added brace-counting algorithm to properly extract variables with insertion codes
- Fixed explicit type parameter detection (textarea, text, email, etc.)
- Made scheduled_task optional on PluginInstallation model
- Added EventPlugin through model for event-plugin relationships
- Added Event.execute_plugins() method for plugin automation
## Frontend Changes:
- Created Tasks.tsx page for managing scheduled tasks
- Enhanced MyPlugins page with clickable plugin cards
- Added edit configuration modal with dynamic form generation
- Implemented escape sequence handling (convert \n, \', etc. for display)
- Added plugin logos to My Plugins page
- Updated type definitions for PluginInstallation interface
- Added insertion code documentation to Plugin Docs
## Plugin System:
- All platform plugins now have editable email templates with textarea support
- Template variables properly parsed with full default values
- Insertion codes ({{CUSTOMER_NAME}}, {{BUSINESS_NAME}}, etc.) documented
- Plugin logos displayed in marketplace and My Plugins
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
354 lines
13 KiB
Python
354 lines
13 KiB
Python
from django.core.management.base import BaseCommand
|
|
from django.utils import timezone
|
|
from schedule.models import PluginTemplate
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = 'Seed platform-owned plugins into the database'
|
|
|
|
def handle(self, *args, **options):
|
|
plugins_data = [
|
|
{
|
|
'name': 'Daily Appointment Summary Email',
|
|
'slug': 'daily-appointment-summary',
|
|
'category': PluginTemplate.Category.EMAIL,
|
|
'short_description': 'Send daily email summary of appointments',
|
|
'description': '''Stay on top of your schedule with automated daily appointment summaries.
|
|
|
|
This plugin sends a comprehensive email digest every morning with:
|
|
- List of all appointments for the day
|
|
- Customer names and contact information
|
|
- Service details and duration
|
|
- Staff/resource assignments
|
|
- Any special notes or requirements
|
|
|
|
Perfect for managers and staff who want to start their day informed and prepared.''',
|
|
'plugin_code': '''from datetime import datetime, timedelta
|
|
|
|
# Get today's appointments
|
|
today = datetime.now().date()
|
|
appointments = api.get_appointments(
|
|
start_date=today.isoformat(),
|
|
end_date=today.isoformat()
|
|
)
|
|
|
|
# Format the appointment list
|
|
summary = f"Daily Appointment Summary - {today}\\n\\n"
|
|
summary += f"Total Appointments: {len(appointments)}\\n\\n"
|
|
|
|
for apt in appointments:
|
|
summary += f"- {apt['title']} at {apt['start_time']}\\n"
|
|
summary += f" Status: {apt['status']}\\n\\n"
|
|
|
|
# Get customizable email settings
|
|
staff_email = '{{PROMPT:staff_email|Staff Email}}'
|
|
email_subject = '{{PROMPT:email_subject|Email Subject|Daily Appointment Summary - {{TODAY}}}}'
|
|
|
|
# Send email
|
|
api.send_email(
|
|
to=staff_email,
|
|
subject=email_subject,
|
|
body=summary
|
|
)
|
|
''',
|
|
'logo_url': '/plugin-logos/daily-appointment-summary.png',
|
|
},
|
|
{
|
|
'name': 'No-Show Customer Tracker',
|
|
'slug': 'no-show-tracker',
|
|
'category': PluginTemplate.Category.REPORTS,
|
|
'short_description': 'Track customers who miss appointments',
|
|
'description': '''Identify patterns of missed appointments and reduce no-shows.
|
|
|
|
This plugin automatically tracks and reports on:
|
|
- Customers who didn\'t show up for scheduled appointments
|
|
- Frequency of no-shows per customer
|
|
- Total revenue lost due to missed appointments
|
|
- Trends over time
|
|
|
|
Helps you identify customers who may need reminder calls or deposits, improving your booking efficiency and revenue.''',
|
|
'plugin_code': '''from datetime import datetime, timedelta
|
|
|
|
# Get configuration
|
|
days_back = int('{{PROMPT:days_back|Days to Look Back|7}}')
|
|
week_ago = (datetime.now() - timedelta(days=days_back)).date()
|
|
today = datetime.now().date()
|
|
|
|
# Get appointments with NOSHOW status
|
|
appointments = api.get_appointments(
|
|
start_date=week_ago.isoformat(),
|
|
end_date=today.isoformat(),
|
|
status='NOSHOW'
|
|
)
|
|
|
|
# Count no-shows per customer
|
|
customer_noshows = {}
|
|
for apt in appointments:
|
|
customer_id = apt.get('customer_id')
|
|
if customer_id:
|
|
customer_noshows[customer_id] = customer_noshows.get(customer_id, 0) + 1
|
|
|
|
# Generate report
|
|
report = f"No-Show Report ({week_ago} to {today})\\n\\n"
|
|
report += f"Total No-Shows: {len(appointments)}\\n"
|
|
report += f"Unique Customers: {len(customer_noshows)}\\n\\n"
|
|
report += "Top Offenders:\\n"
|
|
|
|
for customer_id, count in sorted(customer_noshows.items(), key=lambda x: x[1], reverse=True)[:10]:
|
|
report += f"- Customer {customer_id}: {count} no-shows\\n"
|
|
|
|
# Get customizable email settings
|
|
manager_email = '{{PROMPT:manager_email|Manager Email}}'
|
|
email_subject = '{{PROMPT:email_subject|Email Subject|No-Show Report}}'
|
|
|
|
# Send report
|
|
api.send_email(
|
|
to=manager_email,
|
|
subject=email_subject,
|
|
body=report
|
|
)
|
|
''',
|
|
'logo_url': '/plugin-logos/no-show-tracker.png',
|
|
},
|
|
{
|
|
'name': 'Birthday Greeting Campaign',
|
|
'slug': 'birthday-greetings',
|
|
'category': PluginTemplate.Category.CUSTOMER,
|
|
'short_description': 'Send birthday emails with offers',
|
|
'description': '''Delight your customers with personalized birthday greetings and special offers.
|
|
|
|
This plugin automatically:
|
|
- Identifies customers with birthdays today
|
|
- Sends personalized birthday emails
|
|
- Includes custom discount codes or special offers
|
|
- Helps drive repeat bookings and customer loyalty
|
|
|
|
A simple way to show customers you care while encouraging them to book their next appointment.''',
|
|
'plugin_code': '''# Get all customers with email addresses
|
|
customers = api.get_customers(has_email=True, limit=1000)
|
|
|
|
# Get customizable email template
|
|
discount_code = '{{PROMPT:discount_code|Discount Code}}'
|
|
email_subject = '{{PROMPT:email_subject|Email Subject|Happy Birthday!}}'
|
|
email_body = '{{PROMPT:email_body|Email Message|Happy Birthday {{CUSTOMER_NAME}}!\n\nWe hope you have a wonderful day! As a special birthday gift, we\'d like to offer you {discount_code} on your next appointment.\n\nBook now and treat yourself!\n\nBest wishes,\n{{BUSINESS_NAME}}||textarea}}'
|
|
|
|
# Filter for birthdays today (would need birthday field in customer data)
|
|
# For now, send to all customers as example
|
|
for customer in customers:
|
|
# Format email body with discount code
|
|
formatted_body = email_body.format(discount_code=discount_code)
|
|
|
|
api.send_email(
|
|
to=customer['email'],
|
|
subject=email_subject,
|
|
body=formatted_body
|
|
)
|
|
|
|
api.log(f"Sent {len(customers)} birthday greetings")
|
|
''',
|
|
'logo_url': '/plugin-logos/birthday-greetings.png',
|
|
},
|
|
{
|
|
'name': 'Monthly Revenue Report',
|
|
'slug': 'monthly-revenue-report',
|
|
'category': PluginTemplate.Category.REPORTS,
|
|
'short_description': 'Monthly business statistics',
|
|
'description': '''Get comprehensive monthly insights into your business performance.
|
|
|
|
This plugin generates detailed reports including:
|
|
- Total revenue and number of appointments
|
|
- Revenue breakdown by service type
|
|
- Busiest days and times
|
|
- Most popular services
|
|
- Customer retention metrics
|
|
- Year-over-year comparisons
|
|
|
|
Perfect for owners and managers who want to track business growth and identify opportunities.''',
|
|
'plugin_code': '''from datetime import datetime, timedelta
|
|
|
|
# Get last month's date range
|
|
today = datetime.now()
|
|
first_of_this_month = today.replace(day=1)
|
|
last_month_end = first_of_this_month - timedelta(days=1)
|
|
last_month_start = last_month_end.replace(day=1)
|
|
|
|
# Get all appointments from last month
|
|
appointments = api.get_appointments(
|
|
start_date=last_month_start.isoformat(),
|
|
end_date=last_month_end.isoformat()
|
|
)
|
|
|
|
# Calculate statistics
|
|
total_appointments = len(appointments)
|
|
completed = len([a for a in appointments if a['status'] == 'COMPLETED'])
|
|
canceled = len([a for a in appointments if a['status'] == 'CANCELED'])
|
|
|
|
# Generate report
|
|
month_name = last_month_start.strftime('%B %Y')
|
|
report = f"""Monthly Revenue Report - {month_name}
|
|
|
|
SUMMARY
|
|
-------
|
|
Total Appointments: {total_appointments}
|
|
Completed: {completed}
|
|
Canceled: {canceled}
|
|
Completion Rate: {(completed/total_appointments*100):.1f}%
|
|
|
|
DETAILS
|
|
-------
|
|
"""
|
|
|
|
for apt in appointments[:10]: # Show first 10
|
|
report += f"- {apt['title']} ({apt['status']})\\n"
|
|
|
|
# Get customizable email settings
|
|
owner_email = '{{PROMPT:owner_email|Owner Email}}'
|
|
email_subject = '{{PROMPT:email_subject|Email Subject|Monthly Revenue Report}}'
|
|
|
|
# Send report
|
|
api.send_email(
|
|
to=owner_email,
|
|
subject=email_subject,
|
|
body=report
|
|
)
|
|
''',
|
|
'logo_url': '/plugin-logos/monthly-revenue-report.png',
|
|
},
|
|
{
|
|
'name': 'Appointment Reminder (24hr)',
|
|
'slug': 'appointment-reminder-24hr',
|
|
'category': PluginTemplate.Category.BOOKING,
|
|
'short_description': 'Remind customers 24hrs before appointments',
|
|
'description': '''Reduce no-shows with automated appointment reminders.
|
|
|
|
This plugin sends friendly reminder emails to customers 24 hours before their scheduled appointments, including:
|
|
- Appointment date and time
|
|
- Service details
|
|
- Location/directions
|
|
- Custom message or instructions
|
|
- Cancellation policy reminder
|
|
|
|
Studies show that appointment reminders can reduce no-shows by up to 90%.''',
|
|
'plugin_code': '''from datetime import datetime, timedelta
|
|
|
|
# Get appointments 24 hours from now
|
|
tomorrow = (datetime.now() + timedelta(days=1)).date()
|
|
appointments = api.get_appointments(
|
|
start_date=tomorrow.isoformat(),
|
|
end_date=tomorrow.isoformat(),
|
|
status='SCHEDULED'
|
|
)
|
|
|
|
# Get customizable email template
|
|
email_subject = '{{PROMPT:email_subject|Email Subject|Reminder: Your Appointment Tomorrow}}'
|
|
email_body = '{{PROMPT:email_body|Email Message|Hi {{CUSTOMER_NAME}},\n\nThis is a friendly reminder about your appointment:\n\nDate/Time: {{APPOINTMENT_TIME}}\nService: {{APPOINTMENT_SERVICE}}\n\nPlease arrive 10 minutes early.\n\nIf you need to cancel or reschedule, please let us know as soon as possible.\n\nBest regards,\n{{BUSINESS_NAME}}||textarea}}'
|
|
|
|
# Send reminders
|
|
for apt in appointments:
|
|
customer_id = apt.get('customer_id')
|
|
if customer_id:
|
|
api.send_email(
|
|
to=customer_id,
|
|
subject=email_subject,
|
|
body=email_body
|
|
)
|
|
|
|
api.log(f"Sent {len(appointments)} appointment reminders")
|
|
''',
|
|
'logo_url': '/plugin-logos/appointment-reminder-24hr.png',
|
|
},
|
|
{
|
|
'name': 'Inactive Customer Re-engagement',
|
|
'slug': 'inactive-customer-reengagement',
|
|
'category': PluginTemplate.Category.CUSTOMER,
|
|
'short_description': 'Email inactive customers with offers',
|
|
'description': '''Win back customers who haven\'t booked in a while.
|
|
|
|
This plugin automatically identifies customers who haven\'t made an appointment recently and sends them:
|
|
- Personalized "we miss you" messages
|
|
- Special comeback offers or discounts
|
|
- Reminders of services they previously enjoyed
|
|
- Easy booking links
|
|
|
|
Configurable inactivity period (default: 60 days). A proven strategy for increasing customer lifetime value and reducing churn.''',
|
|
'plugin_code': '''from datetime import datetime, timedelta
|
|
|
|
# Get configuration
|
|
inactive_days = int('{{PROMPT:inactive_days|Days Inactive|60}}')
|
|
discount_code = '{{PROMPT:discount_code|Discount Code}}'
|
|
email_subject = '{{PROMPT:email_subject|Email Subject|We Miss You! Come Back Soon}}'
|
|
email_body = '{{PROMPT:email_body|Email Message|Hi {{CUSTOMER_NAME}},\n\nWe noticed it\'s been a while since your last visit, and we wanted to reach out.\n\nWe\'d love to see you again! As a special welcome back offer, use code {discount_code} on your next appointment.\n\nBook now and let us take care of you!\n\nBest regards,\n{{BUSINESS_NAME}}||textarea}}'
|
|
|
|
# Get recent appointments to find active customers
|
|
cutoff_date = (datetime.now() - timedelta(days=inactive_days)).date()
|
|
recent_appointments = api.get_appointments(
|
|
start_date=cutoff_date.isoformat(),
|
|
end_date=datetime.now().date().isoformat()
|
|
)
|
|
|
|
# Get active customer IDs
|
|
active_customer_ids = set(apt.get('customer_id') for apt in recent_appointments if apt.get('customer_id'))
|
|
|
|
# Get all customers
|
|
all_customers = api.get_customers(has_email=True, limit=1000)
|
|
|
|
# Find inactive customers and send re-engagement emails
|
|
inactive_count = 0
|
|
for customer in all_customers:
|
|
if customer['id'] not in active_customer_ids:
|
|
# Format email body with discount code
|
|
formatted_body = email_body.format(discount_code=discount_code)
|
|
|
|
api.send_email(
|
|
to=customer['email'],
|
|
subject=email_subject,
|
|
body=formatted_body
|
|
)
|
|
inactive_count += 1
|
|
|
|
api.log(f"Sent re-engagement emails to {inactive_count} inactive customers")
|
|
''',
|
|
'logo_url': '/plugin-logos/inactive-customer-reengagement.png',
|
|
},
|
|
]
|
|
|
|
created_count = 0
|
|
skipped_count = 0
|
|
|
|
for plugin_data in plugins_data:
|
|
# Check if plugin already exists by slug
|
|
if PluginTemplate.objects.filter(slug=plugin_data['slug']).exists():
|
|
self.stdout.write(
|
|
self.style.WARNING(f"Skipping '{plugin_data['name']}' - already exists")
|
|
)
|
|
skipped_count += 1
|
|
continue
|
|
|
|
# Create the plugin
|
|
plugin = PluginTemplate.objects.create(
|
|
name=plugin_data['name'],
|
|
slug=plugin_data['slug'],
|
|
category=plugin_data['category'],
|
|
short_description=plugin_data['short_description'],
|
|
description=plugin_data['description'],
|
|
plugin_code=plugin_data['plugin_code'],
|
|
logo_url=plugin_data.get('logo_url', ''),
|
|
visibility=PluginTemplate.Visibility.PLATFORM,
|
|
is_approved=True,
|
|
approved_at=timezone.now(),
|
|
author_name='Smooth Schedule',
|
|
license_type='PLATFORM',
|
|
)
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f"Created plugin: '{plugin.name}'")
|
|
)
|
|
created_count += 1
|
|
|
|
# Summary
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f'\nSuccessfully created {created_count} plugin(s), {skipped_count} already existed.'
|
|
)
|
|
)
|