- TypeScript 97%
- Shell 2.8%
- Dockerfile 0.1%
| plans | ||
| prisma | ||
| scripts | ||
| src | ||
| .eslintrc.json | ||
| .gitignore | ||
| auth.config.ts | ||
| auth.ts | ||
| CHANGELOG.md | ||
| DEPLOYMENT.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| next-auth.d.ts | ||
| next.config.mjs | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| prisma.config.ts | ||
| README.md | ||
| tailwind.config.ts | ||
| task.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
ACC System
Web-based booking schedule for Always Classic Cars, replacing the Excel-based wedding booking spreadsheet.
Features
- Schedule view — bookings grouped by date, dark slate date headers, past dates hidden by default; delete booking is accessed from the Edit Booking page (not the schedule) to prevent accidental deletion
- Multi-car bookings — one job can assign multiple cars and chauffeurs
- Inline editing — change chauffeur, pickup time, and duration directly from the schedule (desktop); mobile shows read-only card view — tap the pencil icon to open the full booking editor
- Chauffeur status — gray (planning), blue (sent), green (accepted); when sent, tick/cross buttons let the chauffeur accept or decline directly from the schedule
- Payment tracking — Waiting Deposit, Deposit Paid, Cash on Day, PIF, Hold (badges on schedule and print); per-car pricing with travel cost, discount, and running total; record individual payments (receipt number, date, amount) with live Amount Paid and Balance; deposit required shown as $100/car or full payment if booking is within 2 weeks; saved payments are read-only (delete only) to prevent accidental edits
- Month/year navigation — click the month or year on the schedule to jump directly to any period
- Victorian public holidays — displayed as badges on the schedule, fetched from the official VIC Gov iCal feed
- Fleet management — full CRUD for cars; inactive cars are preserved if they have bookings
- Chauffeur management — full CRUD for chauffeurs with licence, DOB, address, and DC licence fields; same soft-delete protection
- Chauffeur availability calendar — mark chauffeurs unavailable for a day or date range (full day or time window); visible in the schedule dropdown; chauffeur portal shows a monthly calendar grid view
- Availability warnings — unavailable or double-booked chauffeurs show with strikethrough in dropdowns; selecting one prompts a confirmation warning on both the schedule and booking edit pages
- No more bookings toggle per date
- Authentication — login required; session-based with JWT
- Settings — manage admin accounts, booking sources, and integrations (add, edit, delete)
- Traccar GPS tracking — optional integration with a Traccar GPS server; on multi-car jobs the chauffeur portal shows a "Cars Location" link that generates a day-limited share link showing all cars on the job in real time; uses device share + permissions (no group creation or device group reassignment)
- Mobile friendly — responsive layout with hamburger drawer, card views, horizontal scroll on tables; mobile booking cards show badge, type, and sheet links on the first row with client name below; car cards show car, chauffeur, time, and route on a single wrapping line
- Print schedule — date range picker opens a print-ready view showing only dates with bookings
- Booking type — tag each booking with a type (Wedding, Birthday, School Formal, etc.); types are configurable from Settings and shown as a badge on the schedule
- WPForms webhooks — webhook endpoints receive form submissions and auto-create bookings; wedding form (
POST /api/webhooks/wpforms/wedding), general booking form (POST /api/webhooks/wpforms/general, supporting up to 6 vehicles with pickup/drop-off addresses, "same as previous" address inheritance, signature, booking type, photographer/videographer/contact-on-day), airport transfer form (POST /api/webhooks/wpforms/airport-transfer, supporting Airport Drop-off, Airport Pick-up, and Round Trip — round trips create two separate bookings on the respective departure and return dates with cross-reference notes), and transfer form (POST /api/webhooks/wpforms/transfer, same Drop-off/Pick-up/Round Trip logic with real pickup and drop-off addresses on both legs) - Schedule search — search box in the schedule filter bar; searches client names, emails, phone numbers, partner details, wedding planner, contact on day, notes, car names, chauffeur names, and routes; defaults to future bookings; Past / Future / This month filter pills narrow results further; paginated (20 dates per page) with Previous/Next controls
- iCal calendar feed — chauffeurs subscribe to a personal secret URL in Apple Calendar or Google Calendar; jobs sync automatically with pickup time, car, client name, and address; generated from My Profile; regenerating the link invalidates the old one
- Overtime on Day — chauffeur portal shows a "Record Overtime" button on today's job cards; chauffeur enters actual completion time, captures the client's signature on screen, and records the signer's name; admin can view, edit, or clear overtime from the Edit Booking form; cost is auto-calculated (⌈minutesOver / 15⌉ × $75, adjustable); completion time, cost, and signature shown on the client PDF; an overtime sign-off box is always printed on the chauffeur PDF
- PDF booking sheet import — upload a booking PDF to pre-fill a new booking (cars, chauffeurs, times, client details, payment status, Booking Sheet URL auto-populated from OneDrive path); wedding imports also populate partner names/emails/phones, ceremony address, bride arrival time, reception address, wedding planner, contact on day, photographer, videographer, and per-car pickup address and pickup who; re-importing on an existing booking preserves each car's chauffeur status, assigned chauffeur, and notes; pasting an external signature URL into the Signature Image URL field and saving auto-downloads and stores it locally so it can be embedded in PDFs
- PDF booking sheet generation — client and chauffeur PDFs auto-generated on WPForms webhook import and every booking save; saved to OneDrive with separate Client and Chauffeur Booking Sheet URL fields; chauffeur portal links to the chauffeur-specific PDF; wedding PDFs show Wedding Date, Partner 1/2 columns, Drink Type, and ceremony/reception sections; general booking PDFs show Booking Date, single client name/email/phone, and per-vehicle drop-off addresses; airport transfer PDFs include a Flight Details section (terminal, flight number, departure/arrival times); vehicles grouped by pickup person/address with cost breakdown; night transfer vehicles shown separately; overtime box on chauffeur PDF applies to all booking types; deleting a booking moves the job folder to a
Cancelledsub-folder within the same year/month directory; PDFs are skipped for Hold bookings - Office signatures — manage staff signature images in Settings → Office Signatures; select the signatory per booking via a dropdown on the Edit Booking form; client signature (left) and office signature (right) appear at the end of the client PDF with signer name and Filled Out Date
- Terms & Conditions — manage T&C text in Settings → Terms & Conditions; printed as a second page on the client booking PDF
- Night transfer flag — mark a car row as a night transfer; shown with indigo shading on the schedule
- Client details — store client names, email, and phone on each booking
- Chauffeur portal — chauffeurs log in with their own credentials and see only their upcoming jobs, availability calendar, and profile; portal access is created and managed by admins from the chauffeur's profile page; an amber banner appears on all portal pages when profile fields are missing or the DC licence is not active on CPVV
- Email password reset — chauffeurs can reset their password via a "Forgot password?" link on the login page; a time-limited link is emailed to their address; when admin creates portal access the chauffeur receives a set-password link automatically (no admin-set password required)
- CPVV DC licence verification — verify a chauffeur's Driver's Certificate against the CPVV (Commercial Passenger Vehicles Victoria) public register; Verify button on the admin and chauffeur profile forms shows live name and accreditation status; DC ✓/✗ status badges on the chauffeurs list; saving with an unverified DC number prompts a confirmation; ABN field on chauffeur profile for invoicing
- Reports — admin-only Reports tab with year-filtered dashboard: summary stats, bookings per month, jobs per car, chauffeur utilization, bookings by type/source/payment status, and a cross-year jobs-per-car grouped chart
- Activity Log — admin-only audit trail recording every booking create, update, and delete with field-level diffs (old → new values); covers booking fields, car assignment changes (pickup time, chauffeur, duration, costs, addresses), and payment changes (receipt number, date, amount); chauffeur names resolved; filterable by action type and searchable by record or user; "View activity" button on the Edit Booking form jumps directly to that booking's history
Tech Stack
- Next.js 15 (App Router, TypeScript, sharp for image optimisation)
- Prisma 7 with
@prisma/adapter-better-sqlite3(SQLite) - NextAuth.js v5 (credentials provider, JWT sessions)
- Tailwind CSS
- React Hook Form + Zod
- date-fns
- lucide-react
- Docker + docker-compose (production)
Installation
Production — Debian / Ubuntu / Proxmox LXC (recommended)
One-line install on any fresh Debian 12 or Ubuntu 22.04+ system:
curl -fsSL https://git.bennellit.com.au/sb/ACC-System/raw/branch/main/scripts/install.sh | sudo bash
This installs Node.js 20, clones the repo, builds the app, configures PM2, and creates the first admin account. See DEPLOYMENT.md for the full guide and Docker alternative.
To update an existing installation:
curl -fsSL https://git.bennellit.com.au/sb/ACC-System/raw/branch/main/scripts/update.sh | sudo bash
Production — Docker
docker compose up --build
The app runs on port 3000. The SQLite database is stored in a named Docker volume (acc_data) and survives container rebuilds.
Set AUTH_SECRET and APP_URL as environment variables — do not commit them to source control.
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=file:/data/acc.db
- AUTH_SECRET=<your-secret>
- APP_URL=https://yourdomain.com
volumes:
- acc_data:/data
restart: unless-stopped
volumes:
acc_data:
Development
Prerequisites
- Node.js 18+
- npm
# Install dependencies
npm install
# Apply database migrations and generate Prisma client
npx prisma migrate deploy
npx prisma generate
# Seed cars and chauffeurs
npx tsx prisma/seed.ts
# Create the first admin account
npx tsx prisma/create-user.ts admin@example.com password123 "Admin"
# Start the dev server
npm run dev
# Run unit tests (112 tests — time-utils, car-utils, overtime-utils, pdf-parse-utils)
npm test
Open http://localhost:3000. You will be redirected to the login page.
Environment
Create a .env file at the project root:
DATABASE_URL="file:./prisma/dev.db"
AUTH_SECRET="<generate with: node -e \"require('crypto').randomBytes(32).toString('base64')\">"
APP_URL="http://localhost:3000" # set to your public URL in production (used in password reset emails)
# WPForms webhooks (shared secret for all three webhook endpoints)
WPFORMS_WEBHOOK_SECRET="<generate with: node -e \"require('crypto').randomBytes(32).toString('hex')\">"
# Signature image storage (defaults to /data/signatures in production)
# SIGNATURES_PATH="/data/signatures"
# Traccar GPS tracking (optional — enable in Settings → Integrations)
TRACCAR_USERNAME="admin@example.com"
TRACCAR_PASSWORD="yourpassword"
# Internal URL for server-side Traccar API calls (use direct IP to bypass Cloudflare/proxy)
# TRACCAR_INTERNAL_URL="http://192.168.1.x:8082"
Database Schema
BookingDate 1 ──< Booking 1 ──< BookingCar >── Car
│ >── Chauffeur
1──< BookingPayment
>── OfficeSignature
ChauffeurUnavailability >── Chauffeur
User >── Chauffeur (optional — chauffeur portal accounts)
User 1──< PasswordResetToken
| Model | Purpose |
|---|---|
BookingDate |
One record per calendar date |
Booking |
One job (payment, client details, notes, booking sheet URL, flight details, overtime) |
BookingCar |
One car+chauffeur assignment within a job (time, duration, route, night transfer flag, overtime per car) |
BookingPayment |
Individual payment records against a booking (receipt number, date, amount) |
Car |
Fleet vehicle |
Chauffeur |
Chauffeur with profile fields (licence, DOB, address, ABN) |
ChauffeurUnavailability |
Unavailability record per chauffeur per day (optional time window and reason) |
BookingSource |
Configurable booking source list (Website, EasyWeddings, etc.) |
BookingType |
Configurable booking type list (Wedding, Birthday, Airport Transfer, etc.) |
OfficeSignature |
Staff signature image + name for printing on client PDFs |
User |
Admin or chauffeur portal login account |
PasswordResetToken |
Time-limited tokens for chauffeur password reset / setup emails |
SystemSettings |
Singleton row for global feature flags (Traccar enabled, base URL, T&C text) |
BookingTraccarShare |
Cache of Traccar group share tokens per booking + date |
Project Structure
src/
├── app/
│ ├── schedule/ # Main schedule view (RSC)
│ ├── bookings/ # New & edit booking forms
│ ├── cars/ # Fleet management
│ ├── chauffeurs/ # Chauffeur management + availability calendar
│ ├── settings/ # Settings (admin accounts, booking sources)
│ ├── reports/ # Reports dashboard
│ └── api/ # REST API routes
├── components/
│ ├── layout/Sidebar.tsx
│ ├── schedule/ # ScheduleView, DateHeader, BookingGroup, BookingCarRow
│ ├── chauffeurs/ # AvailabilityCalendar
│ ├── reports/ # StatCard, YearSelector, Charts
│ ├── forms/ # BookingForm, ChauffeurForm, PdfImportButton
│ └── ui/ # Badge, ConfirmDialog
└── lib/
├── prisma.ts # Singleton PrismaClient (Prisma 7 driver adapter)
├── schemas.ts # Zod validation schemas
├── availability.ts # Shared overlap/unavailability logic
├── reports.ts # Aggregation queries for Reports page
├── utils.ts # formatTime, formatDuration, cn()
├── vicHolidays.ts # Victorian public holidays iCal fetcher
└── cpvv.ts # CPVV public register DC licence lookup
auth.ts # NextAuth config (credentials + Prisma)
auth.config.ts # Edge-safe auth config (used by middleware)
src/middleware.ts # JWT-based route protection
prisma/
├── schema.prisma
├── seed.ts # Cars and chauffeurs
├── create-user.ts # CLI script to create admin accounts
└── migrations/
OneDrive Sync (Booking Sheets)
The ACC System can serve booking sheet PDFs directly from the server, synced from OneDrive, so chauffeurs never need a Microsoft login to open them.
1. Install the onedrive client
Follow the official Debian installation guide (abraunegg/onedrive): https://github.com/abraunegg/onedrive/blob/master/docs/ubuntu-package-install.md
2. Authenticate (one-time)
Run as the user the service will run under. It prints a URL — open it in any browser, sign in with the Always Classic Cars Microsoft 365 account, then paste the response URL back into the terminal:
onedrive
Auth tokens are stored in ~/.config/onedrive/ — back these up.
3. Create the configuration files
mkdir -p /var/www/acc-system/onedrive
cat > ~/.config/onedrive/config << 'EOF'
sync_dir = "/var/www/acc-system/onedrive"
threads = "2"
monitor_interval = "30"
EOF
cat > ~/.config/onedrive/sync_list << 'EOF'
Documents/ACC Bookings
Pictures/000 wedding feedback photos/Uploaded
EOF
Adjust threads to match the number of CPU cores on the server.
4. Run the initial sync
Required once after any config change:
onedrive --sync --resync
Files will appear under:
/var/www/acc-system/onedrive/Documents/ACC Bookings//var/www/acc-system/onedrive/Pictures/000 wedding feedback photos/Uploaded/
5. Create the systemd service
--single-directory only supports one path, so the service runs without it — sync_list restricts what is synced.
printf '[Unit]\nDescription=OneDrive Sync\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nUser=root\nExecStart=/usr/bin/onedrive --monitor\nRestart=on-failure\nRestartSec=3\n\n[Install]\nWantedBy=multi-user.target\n' > /etc/systemd/system/onedrive.service
systemctl daemon-reload
systemctl enable onedrive
systemctl start onedrive
systemctl status onedrive
The sync_list file limits the sync to only the two configured folders.
The service starts automatically on boot.
6. Configure the app
Add to .env:
# Linux / Debian production server
ONEDRIVE_SYNC_PATH="/var/www/acc-system/onedrive/Documents/ACC Bookings"
ONEDRIVE_PHOTOS_PATH="/var/www/acc-system/onedrive/Pictures/000 wedding feedback photos/Uploaded"
# Windows development (use forward slashes)
ONEDRIVE_SYNC_PATH="C:/Users/sb/OneDrive - Always Classic Cars/Documents/ACC Bookings"
ONEDRIVE_PHOTOS_PATH="C:/Users/sb/OneDrive - Always Classic Cars/Pictures/000 wedding feedback photos/Uploaded"
# Traccar GPS tracking (optional — enable in Settings → Integrations)
TRACCAR_USERNAME="admin@example.com"
TRACCAR_PASSWORD="yourpassword"
# Use direct IP to bypass Cloudflare/reverse-proxy for server-side API calls
# TRACCAR_INTERNAL_URL="http://192.168.1.x:8082"
WPForms Webhooks
When a customer submits a booking form on the website, WPForms can POST the data directly to ACC System, automatically creating a booking so staff don't need to re-enter enquiries. There are two webhook endpoints — one for the Wedding form and one for the General Booking form. Both share the same secret.
1. Add the env var
Add to .env:
SECRET=$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))') && echo "WPFORMS_WEBHOOK_SECRET=\"$SECRET\"" >> /opt/acc-system/.env
2. Configure WPForms — Wedding form
In WordPress: WPForms → (Wedding form) → Settings → Webhooks → Add New
| Setting | Value |
|---|---|
| Request URL | https://your-domain.com/api/webhooks/wpforms/wedding?secret=YOUR_SECRET |
| Request Method | POST |
| Request Format | JSON |
| Enable | ✓ |
Under Request Body, add one row per field using the + button. Leave the Secret field empty (the secret is already in the URL).
Important: For each row, type the key name in the left column, then use the dropdown on the right to select the matching form field — do not type the field ID as text. The field ID column below is only to help you identify the correct field in the dropdown.
| Key | Field ID |
|---|---|
date |
7 |
contactName |
13 |
contactEmail |
16 |
contactPhone |
17 |
role |
15 |
otherRole |
19 |
partner1Name |
18 |
partner1Email |
65 |
partner1Phone |
66 |
partner2Name |
67 |
partner2Email |
69 |
partner2Phone |
71 |
nightTransferOnly |
138 |
vehicle1 |
30 |
vehicle1Who |
33 |
vehicle1Address |
35 |
vehicle1Time |
37 |
vehicle1Hours |
51 |
requireVehicle2 |
41 |
vehicle2 |
40 |
vehicle2Who |
45 |
vehicle2Address |
46 |
vehicle2Time |
52 |
vehicle2Hours |
55 |
requireVehicle3 |
63 |
vehicle3 |
74 |
vehicle3Who |
75 |
vehicle3Address |
77 |
vehicle3Time |
78 |
vehicle3Hours |
82 |
requireVehicle4 |
83 |
vehicle4 |
85 |
vehicle4Who |
86 |
vehicle4Address |
88 |
vehicle4Time |
89 |
vehicle4Hours |
93 |
requireVehicle5 |
94 |
vehicle5 |
96 |
vehicle5Who |
97 |
vehicle5Address |
100 |
vehicle5Time |
101 |
vehicle5Hours |
104 |
requireVehicle6 |
106 |
vehicle6 |
108 |
vehicle6Who |
109 |
vehicle6Address |
111 |
vehicle6Time |
112 |
vehicle6Hours |
115 |
ceremonyAddress |
119 |
brideArrivalTime |
120 |
ceremonyTime |
166 |
groomArrivalTime |
165 |
receptionAddress |
122 |
receptionTime |
123 |
requireNightTransfer |
126 |
nightVehicle1 |
139 |
nightAddress1 |
125 |
nightTime1 |
127 |
requireNightVehicle2 |
140 |
nightVehicle2 |
141 |
nightAddress2 |
142 |
nightTime2 |
143 |
contactOnDay |
130 |
photographer |
131 |
videographer |
129 |
drinkType |
164 |
specialInstructions |
132 |
entryId |
Entry ID — select Entry ID from the "Other" section of the dropdown (not a regular field) |
formSubmittedDate |
134 |
signatureUrl |
136 |
signedBy |
137 |
3. Configure WPForms — General Booking form
In WordPress: WPForms → (General Booking 2026 form) → Settings → Webhooks → Add New
| Setting | Value |
|---|---|
| Request URL | https://your-domain.com/api/webhooks/wpforms/general?secret=YOUR_SECRET |
| Request Method | POST |
| Request Format | JSON |
| Enable | ✓ |
Same rules apply — type the key name on the left, select the field from the dropdown on the right.
| Key | Field ID |
|---|---|
date |
7 |
contactName |
13 |
contactEmail |
16 |
contactPhone |
17 |
bookingType |
29 |
bookingTypeOther |
19 |
vehicle1 |
30 |
vehicle1PickupAddress |
35 |
vehicle1PickupTime |
37 |
vehicle1DropoffAddress |
43 |
vehicle1DropoffTime |
44 |
vehicle1Hours |
51 |
requireVehicle2 |
41 |
vehicle2 |
40 |
vehicle2SamePickup |
59 |
vehicle2PickupAddress |
46 |
vehicle2PickupTime |
52 |
vehicle2SameDropoff |
73 |
vehicle2DropoffAddress |
53 |
vehicle2DropoffTime |
54 |
vehicle2Hours |
55 |
requireVehicle3 |
63 |
vehicle3 |
74 |
vehicle3SamePickup |
76 |
vehicle3PickupAddress |
77 |
vehicle3PickupTime |
78 |
vehicle3SameDropoff |
79 |
vehicle3DropoffAddress |
80 |
vehicle3DropoffTime |
81 |
vehicle3Hours |
82 |
requireVehicle4 |
83 |
vehicle4 |
85 |
vehicle4SamePickup |
87 |
vehicle4PickupAddress |
88 |
vehicle4PickupTime |
89 |
vehicle4SameDropoff |
90 |
vehicle4DropoffAddress |
91 |
vehicle4DropoffTime |
92 |
vehicle4Hours |
93 |
requireVehicle5 |
94 |
vehicle5 |
96 |
vehicle5SamePickup |
98 |
vehicle5PickupAddress |
100 |
vehicle5PickupTime |
101 |
vehicle5SameDropoff |
102 |
vehicle5DropoffAddress |
103 |
vehicle5DropoffTime |
105 |
vehicle5Hours |
104 |
requireVehicle6 |
106 |
vehicle6 |
108 |
vehicle6SamePickup |
110 |
vehicle6PickupAddress |
111 |
vehicle6PickupTime |
112 |
vehicle6SameDropoff |
113 |
vehicle6DropoffAddress |
114 |
vehicle6DropoffTime |
116 |
vehicle6Hours |
115 |
photographer |
131 |
videographer |
129 |
contactOnDay |
130 |
specialInstructions |
132 |
entryId |
Entry ID — select Entry ID from the "Other" section of the dropdown (not a regular field) |
formSubmittedDate |
134 |
signatureUrl |
136 |
signedBy |
137 |
4. Configure WPForms — Airport Transfer form
In WordPress: WPForms → (Airport Transfer form) → Settings → Webhooks → Add New
| Setting | Value |
|---|---|
| Request URL | https://your-domain.com/api/webhooks/wpforms/airport-transfer?secret=YOUR_SECRET |
| Request Method | POST |
| Request Format | JSON |
| Enable | ✓ |
Same rules apply — type the key name on the left, select the field from the dropdown on the right.
| Key | Field |
|---|---|
contactName |
Contact name |
contactEmail |
Contact email |
contactPhone |
Contact phone |
airportService |
Service type ("Airport Drop-off", "Airport Pick-up", or "Round Trip") |
vehicle |
Vehicle preference |
departureDate |
Departure date (Drop-off / Round Trip) |
pickupAddress |
Pickup address (Drop-off) |
pickupTime |
Pickup time (Drop-off) |
airportArrivalTime |
Time car must arrive at airport terminal (Drop-off) |
dropoffTerminal |
Airport terminal (Drop-off) |
flightDepartureTime |
Flight departure time (Drop-off) |
returnDate |
Return date (Pick-up / Round Trip) |
dropoffAddress |
Drop-off address (Pick-up) |
flightNumber |
Flight number (Pick-up) |
pickupTerminal |
Airport terminal (Pick-up) |
flightArrivalTime |
Flight arrival time (Pick-up) |
specialInstructions |
Special instructions |
entryId |
Entry ID — select Entry ID from the "Other" section |
formSubmittedDate |
Form submitted date |
signatureUrl |
Signature URL |
signedBy |
Signed by |
5. Configure WPForms — Transfer form
In WordPress: WPForms → (Transfer form) → Settings → Webhooks → Add New
| Setting | Value |
|---|---|
| Request URL | https://your-domain.com/api/webhooks/wpforms/transfer?secret=YOUR_SECRET |
| Request Method | POST |
| Request Format | JSON |
| Enable | ✓ |
Same rules apply — type the key name on the left, select the field from the dropdown on the right.
| Key | Field ID |
|---|---|
entryId |
Entry ID — select Entry ID from the "Other" section |
transferService |
145 |
contactName |
13 |
contactEmail |
16 |
contactPhone |
17 |
departureDate |
148 |
pickupAddress |
149 |
pickupTime |
150 |
dropoffAddress |
162 |
returnDate |
154 |
returnPickupAddress |
155 |
returnPickupTime |
159 |
returnDropoffAddress |
163 |
vehicle |
160 |
specialInstructions |
132 |
formSubmittedDate |
134 |
signatureUrl |
136 |
signedBy |
137 |
6. How it works
- Vehicles are fuzzy-matched against active fleet cars; unmatched vehicles appear in the booking notes
- General Booking: when a vehicle's pickup or drop-off is marked "same as previous vehicle", the address is inherited from the preceding slot
- Wedding: for night transfers, pickup address is set from the reception address, drop-off from the night transfer field, duration defaults to 1 hour
- Airport Transfer: a Round Trip submission creates two separate bookings — one on the departure date (Drop-off) and one on the return date (Pick-up) — with cross-reference notes linking them; "Airport Transfer" booking type is created automatically if it doesn't exist
- Transfer: same Round Trip → two bookings behaviour; pickup and drop-off addresses on both legs; "Transfer" booking type is created automatically if it doesn't exist
- Bookings are created with status Waiting Deposit (Hold bookings skip PDF generation)
- Duplicate submissions (same WPForms entry ID) are silently ignored
- If the webhook fails, WPForms email notifications act as a fallback — staff can manually create the booking from the notification email
Version
v1.41.0 — see CHANGELOG.md