feat: add booking workflow for service businesses (salons, clinics, massage)

This commit is contained in:
¨NW¨
2026-04-05 01:27:25 +01:00
parent 884ef3d57a
commit 14b2cb2742
3 changed files with 2198 additions and 1 deletions

View File

@@ -443,7 +443,8 @@ Provider availability depends on configuration. Common providers include:
|---------|-------------|-------|
| `/landing-page` | Create landing page CMS from HTML mockups | ollama-cloud/kimi-k2.5 |
| `/commerce` | Create e-commerce site with products, cart, payments | qwen/qwen3-coder:free |
| `/blog` | Create blog/CMS with posts, comments, SEO | qwen/qwen3-coder:free |
| `/blog` | Create blog/CMS with posts, comments, SEO | qwen/qeen3-coder:free |
| `/booking` | Create booking system for services/appointments | qwen/qwen3-coder:free |
| `/pipeline` | Run full agent pipeline for issue | - |
| `/feature` | Full feature development pipeline | qwen/qwen3-coder:free |
| `/code` | Quick code generation | qwen/qwen3-coder:free |
@@ -510,6 +511,22 @@ Provider availability depends on configuration. Common providers include:
- RSS/Atom feeds and sitemap generation
- Media library management
### Booking System Domain
**Location**: `.kilo/skills/booking/SKILL.md`
**Purpose**: Domain knowledge for building booking and appointment systems.
**Capabilities**:
- Service management with categories and pricing
- Staff scheduling and availability
- Real-time slot calculation
- Booking flow (service → staff → date/time → customer)
- Status management (pending, confirmed, completed, cancelled)
- Email/SMS notifications
- Calendar integration (Google, iCal)
- Revenue and utilization reports
---
## File Naming Conventions

1541
.kilo/commands/booking.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,639 @@
---
name: booking
description: Booking domain knowledge - appointments, services, staff, schedules, reservations
---
# Booking Skill
## Purpose
Provides domain knowledge for building booking and appointment systems: services, staff, schedules, availability, reservations, notifications.
## Capabilities
### Service Management
- Service categories
- Service duration and pricing
- Service variants (different durations)
- Required resources
### Staff Management
- Staff profiles
- Role-based permissions
- Specializations
- Availability schedules
### Booking Flow
- Service selection
- Staff selection (optional)
- Date/time selection
- Customer details
- Confirmation
- Payment (optional)
### Scheduling
- Working hours
- Break times
- Days off
- Multi-location support
- Timezone handling
### Notifications
- Email confirmations
- SMS reminders
- Calendar sync (Google, iCal)
- Push notifications
### Admin Features
- Booking management
- Availability editor
- Reports and analytics
- Customer management
## Database Schema
### Services
```sql
CREATE TABLE services (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER,
name TEXT NOT NULL,
description TEXT,
duration INTEGER NOT NULL, -- minutes
price DECIMAL(10, 2) NOT NULL,
buffer_time INTEGER DEFAULT 0, -- minutes between appointments
max_per_slot INTEGER DEFAULT 1,
is_active BOOLEAN DEFAULT 1,
image_url TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES service_categories(id)
);
CREATE TABLE service_categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
description TEXT,
image_url TEXT,
sort_order INTEGER DEFAULT 0
);
CREATE TABLE service_staff (
service_id INTEGER NOT NULL,
staff_id INTEGER NOT NULL,
custom_price DECIMAL(10, 2),
custom_duration INTEGER,
PRIMARY KEY (service_id, staff_id),
FOREIGN KEY (service_id) REFERENCES services(id),
FOREIGN KEY (staff_id) REFERENCES staff(id)
);
```
### Staff
```sql
CREATE TABLE staff (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER UNIQUE,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
phone TEXT,
avatar_url TEXT,
bio TEXT,
role TEXT DEFAULT 'staff', -- 'admin', 'manager', 'staff'
is_active BOOLEAN DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE staff_schedules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
staff_id INTEGER NOT NULL,
day_of_week INTEGER NOT NULL, -- 0=Sunday, 6=Saturday
start_time TEXT NOT NULL, -- '09:00'
end_time TEXT NOT NULL, -- '17:00'
break_start TEXT, -- '12:00'
break_end TEXT, -- '13:00'
is_working BOOLEAN DEFAULT 1,
FOREIGN KEY (staff_id) REFERENCES staff(id)
);
CREATE TABLE staff_time_off (
id INTEGER PRIMARY KEY AUTOINCREMENT,
staff_id INTEGER NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
reason TEXT,
FOREIGN KEY (staff_id) REFERENCES staff(id)
);
```
### Bookings
```sql
CREATE TABLE bookings (
id TEXT PRIMARY KEY, -- UUID
booking_number TEXT UNIQUE NOT NULL,
service_id INTEGER NOT NULL,
staff_id INTEGER,
customer_id INTEGER,
-- Customer info (guest bookings allowed)
customer_name TEXT NOT NULL,
customer_email TEXT NOT NULL,
customer_phone TEXT,
customer_notes TEXT,
-- Appointment details
booking_date DATE NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
-- Status
status TEXT DEFAULT 'pending', -- 'pending', 'confirmed', 'completed', 'cancelled', 'no_show'
-- Pricing
service_price DECIMAL(10, 2) NOT NULL,
addons_total DECIMAL(10, 2) DEFAULT 0,
discount DECIMAL(10, 2) DEFAULT 0,
total DECIMAL(10, 2) NOT NULL,
payment_method TEXT, -- 'cash', 'card', 'online'
payment_status TEXT DEFAULT 'pending', -- 'pending', 'paid', 'refunded'
-- Metadata
source TEXT DEFAULT 'website', -- 'website', 'phone', 'walk_in'
notes TEXT,
internal_notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (service_id) REFERENCES services(id),
FOREIGN KEY (staff_id) REFERENCES staff(id),
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
CREATE TABLE booking_addons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
booking_id TEXT NOT NULL,
addon_id INTEGER NOT NULL,
price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (booking_id) REFERENCES bookings(id),
FOREIGN KEY (addon_id) REFERENCES service_addons(id)
);
-- Availability cache for fast queries
CREATE TABLE availability_slots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
service_id INTEGER NOT NULL,
staff_id INTEGER,
date DATE NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
available BOOLEAN DEFAULT 1,
booking_id TEXT,
FOREIGN KEY (service_id) REFERENCES services(id),
FOREIGN KEY (staff_id) REFERENCES staff(id),
FOREIGN KEY (booking_id) REFERENCES bookings(id)
);
```
### Customers
```sql
CREATE TABLE customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
phone TEXT,
date_of_birth DATE,
notes TEXT,
total_visits INTEGER DEFAULT 0,
total_spent DECIMAL(10, 2) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_customers_email ON customers(email);
CREATE INDEX idx_customers_phone ON customers(phone);
```
### Settings
```sql
CREATE TABLE booking_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
type TEXT DEFAULT 'string' -- 'string', 'number', 'boolean', 'json'
);
-- Default settings
INSERT INTO booking_settings (key, value, type) VALUES
('timezone', 'Europe/Moscow', 'string'),
('currency', 'RUB', 'string'),
('booking_interval', '30', 'number'),
('min_booking_notice', '60', 'number'), -- minutes
('max_booking_advance', '30', 'number'), -- days
('require_phone', '1', 'boolean'),
('require_payment', '0', 'boolean'),
('deposit_percentage', '20', 'number'),
('cancellation_hours', '24', 'number'),
('reminder_hours', '2', 'number'),
('confirmation_sms', '0', 'boolean'),
('confirmation_email', '1', 'boolean');
```
## API Endpoints
### Public API
```yaml
# Services
GET /api/services # List all active services
GET /api/services/:id # Get service details
GET /api/categories # List service categories
# Staff
GET /api/staff # List active staff
GET /api/staff/:id # Get staff details
GET /api/staff/:id/availability # Get staff availability
# Availability
GET /api/availability # Get available slots
POST /api/availability/check # Check specific slot availability
# Booking
POST /api/bookings # Create booking (guest)
GET /api/bookings/:id # Get booking details
POST /api/bookings/:id/cancel # Cancel booking
POST /api/bookings/:id/reschedule # Reschedule booking
# Customer
GET /api/customer/bookings # Get customer's bookings (auth)
POST /api/customer/register # Register customer
```
### Admin API
```yaml
# Services
GET /api/admin/services # List all services
POST /api/admin/services # Create service
PUT /api/admin/services/:id # Update service
DELETE /api/admin/services/:id # Delete service
# Categories
GET /api/admin/categories # List categories
POST /api/admin/categories # Create category
PUT /api/admin/categories/:id # Update category
DELETE /api/admin/categories/:id # Delete category
# Staff
GET /api/admin/staff # List all staff
POST /api/admin/staff # Add staff member
PUT /api/admin/staff/:id # Update staff
DELETE /api/admin/staff/:id # Remove staff
PUT /api/admin/staff/:id/schedule # Update schedule
POST /api/admin/staff/:id/time-off # Add time off
# Bookings
GET /api/admin/bookings # List bookings (filters)
GET /api/admin/bookings/:id # Get booking details
PUT /api/admin/bookings/:id/confirm # Confirm booking
PUT /api/admin/bookings/:id/complete # Mark complete
PUT /api/admin/bookings/:id/cancel # Cancel booking
PUT /api/admin/bookings/:id/no-show # Mark no-show
# Calendar
GET /api/admin/calendar # Calendar view
GET /api/admin/calendar/staff/:id # Staff calendar
# Customers
GET /api/admin/customers # List customers
GET /api/admin/customers/:id # Customer details
GET /api/admin/customers/:id/history # Booking history
# Reports
GET /api/admin/reports/revenue # Revenue report
GET /api/admin/reports/services # Service popularity
GET /api/admin/reports/staff # Staff utilization
GET /api/admin/reports/trends # Booking trends
# Settings
GET /api/admin/settings # Get all settings
PUT /api/admin/settings # Update settings
```
## Availability Logic
### Get Available Slots
```javascript
// services/availability.js
async function getAvailableSlots(serviceId, staffId, date) {
const service = await db.services.findById(serviceId);
const dayOfWeek = new Date(date).getDay();
// Get staff schedule
const schedule = await db.staffSchedules.findOne({
staff_id: staffId,
day_of_week: dayOfWeek,
is_working: true
});
if (!schedule) return [];
// Get existing bookings
const bookings = await db.bookings.find({
staff_id: staffId,
booking_date: date,
status: { $in: ['pending', 'confirmed'] }
});
// Generate slots
const slots = [];
let currentTime = parseTime(schedule.start_time);
const endTime = parseTime(schedule.end_time);
const bufferTime = service.buffer_time || 0;
while (addMinutes(currentTime, service.duration + bufferTime) <= endTime) {
const slotStart = formatTime(currentTime);
const slotEnd = formatTime(addMinutes(currentTime, service.duration));
// Check if slot is available
const isBooked = bookings.some(b =>
b.start_time <= slotStart && b.end_time >= slotStart ||
b.start_time <= slotEnd && b.end_time >= slotEnd ||
b.start_time >= slotStart && b.end_time <= slotEnd
);
// Check break time
const isBreak = schedule.break_start && (
slotStart >= schedule.break_start && slotStart < schedule.break_end ||
slotEnd > schedule.break_start && slotEnd <= schedule.break_end
);
// Check advance booking limit
const slotDateTime = new Date(`${date}T${slotStart}`);
const isTooSoon = slotDateTime < addMinutes(new Date(), settings.min_booking_notice);
// Check past
const isPast = slotDateTime < new Date();
if (!isBooked && !isBreak && !isTooSoon && !isPast) {
slots.push({
start_time: slotStart,
end_time: slotEnd,
available: true
});
}
currentTime = addMinutes(currentTime, service.duration);
}
return slots;
}
```
### Create Booking
```javascript
// services/booking.js
async function createBooking(bookingData) {
const { service_id, staff_id, date, time, customer_name, customer_email, customer_phone } = bookingData;
// Validate service exists
const service = await db.services.findById(service_id);
if (!service || !service.is_active) {
throw new Error('Service not available');
}
// Get staff or auto-assign
let staff = staff_id ?
await db.staff.findById(staff_id) :
await autoAssignStaff(service_id, date, time);
if (!staff) {
throw new Error('No staff available for this slot');
}
// Check availability
const available = await checkAvailability(service_id, staff.id, date, time);
if (!available) {
throw new Error('Slot already booked');
}
// Calculate end time
const endTime = addMinutes(parseTime(time), service.duration);
// Create booking number
const bookingNumber = generateBookingNumber();
// Create booking
const booking = await db.bookings.create({
id: generateUUID(),
booking_number: bookingNumber,
service_id,
staff_id: staff.id,
customer_name,
customer_email,
customer_phone,
booking_date: date,
start_time: time,
end_time: formatTime(endTime),
status: 'pending',
service_price: service.price,
total: service.price
});
// Create availability slot
await db.availabilitySlots.create({
service_id,
staff_id: staff.id,
date,
start_time: time,
end_time: formatTime(endTime),
available: false,
booking_id: booking.id
});
// Send confirmation
await sendConfirmation(booking);
return booking;
}
```
## Booking Status Flow
```
pending → confirmed → completed
↓ ↓ ↓
cancelled cancelled cancelled → refunded
no_show
```
### Status Transitions
```javascript
const STATUS_FLOW = {
pending: ['confirmed', 'cancelled'],
confirmed: ['completed', 'cancelled', 'no_show'],
completed: ['refunded'],
cancelled: ['refunded'],
no_show: [],
refunded: []
};
function canTransition(currentStatus, newStatus) {
return STATUS_FLOW[currentStatus]?.includes(newStatus) || false;
}
```
## Notifications
### Email Templates
```javascript
// services/notifications/email.js
const bookingConfirmation = (booking, service, staff) => ({
subject: `Booking Confirmed - ${service.name}`,
html: `
<h1>Your Booking is Confirmed!</h1>
<p>Booking #${booking.booking_number}</p>
<h2>Details</h2>
<ul>
<li>Service: ${service.name}</li>
<li>Staff: ${staff.name}</li>
<li>Date: ${formatDate(booking.booking_date)}</li>
<li>Time: ${booking.start_time} - ${booking.end_time}</li>
<li>Price: ${formatCurrency(booking.total)}</li>
</ul>
<p>Location: Your Business Name</p>
<a href="${config.siteUrl}/booking/${booking.id}">Manage Booking</a>
<p>Need to cancel? Please give us ${settings.cancellation_hours} hours notice.</p>
`
});
const bookingReminder = (booking, service, staff) => ({
subject: `Reminder: ${service.name} in 2 hours`,
html: `
<h2>Upcoming Appointment Reminder</h2>
<p>Your appointment is in 2 hours!</p>
<ul>
<li>Service: ${service.name}</li>
<li>Staff: ${staff.name}</li>
<li>Time: ${booking.start_time}</li>
</ul>
`
});
```
### SMS Templates
```javascript
// services/notifications/sms.js
const templates = {
confirmation: (booking, service) =>
`Your ${service.name} booking is confirmed for ${formatDate(booking.booking_date)} at ${booking.start_time}. Booking #${booking.booking_number}`,
reminder: (booking, service) =>
`Reminder: ${service.name} appointment in 2 hours at ${booking.start_time}. Reply C to cancel.`,
cancellation: (booking) =>
`Your booking #${booking.booking_number} has been cancelled.`
};
```
## Calendar Integration
### iCal Export
```javascript
function generateICal(booking, service, staff) {
return `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Business Name//Booking System//EN
BEGIN:VEVENT
DTSTART:${formatICalDate(booking.booking_date, booking.start_time)}
DTEND:${formatICalDate(booking.booking_date, booking.end_time)}
SUMMARY:${service.name} with ${staff.name}
DESCRIPTION:Booking #${booking.booking_number}
LOCATION:Business Address
STATUS:CONFIRMED
END:VEVENT
END:VCALENDAR`;
}
```
## Reports
### Revenue Report
```sql
SELECT
DATE(booking_date) as date,
COUNT(*) as bookings,
SUM(total) as revenue,
AVG(total) as avg_booking
FROM bookings
WHERE status IN ('completed', 'confirmed')
AND booking_date BETWEEN ? AND ?
GROUP BY DATE(booking_date)
ORDER BY date;
```
### Staff Utilization
```sql
SELECT
s.id,
s.name,
COUNT(b.id) as bookings,
SUM(TIMESTAMPDIFF(MINUTE,
CONCAT(b.booking_date, ' ', b.start_time),
CONCAT(b.booking_date, ' ', b.end_time)
)) / 60 as hours_booked,
COUNT(DISTINCT b.booking_date) as days_worked
FROM staff s
LEFT JOIN bookings b ON b.staff_id = s.id
AND b.status IN ('completed', 'confirmed')
AND b.booking_date BETWEEN ? AND ?
GROUP BY s.id
ORDER BY bookings DESC;
```
### Service Popularity
```sql
SELECT
s.id,
s.name,
s.category_id,
COUNT(b.id) as bookings,
SUM(b.total) as revenue
FROM services s
LEFT JOIN bookings b ON b.service_id = s.id
AND b.status IN ('completed', 'confirmed')
AND b.booking_date BETWEEN ? AND ?
GROUP BY s.id
ORDER BY bookings DESC;
```
## Integration Points
- Payment: Stripe, YooKassa
- Calendar: Google Calendar, iCal
- SMS: Twilio, SMS.ru
- Email: SendGrid, Mailgun
- Analytics: Google Analytics, Yandex Metrika
- CRM: Integration API for customer data