# Laravel Zap Documentation Welcome to the Laravel Zap documentation. Get started with scheduling and calendar management for your Laravel applications. ## Contents - **Getting started** — [Introduction](https://laravel-zap.com/docs/getting-started/introduction) (concepts), [Installation](https://laravel-zap.com/docs/getting-started/installation), [Quick start](https://laravel-zap.com/docs/getting-started/quick-start) (one end-to-end example) - **Recurrence & schedule patterns** — [Schedule patterns](https://laravel-zap.com/docs/guides/schedule-patterns) (daily, weekly, monthly, ordinal weekday, dynamic) - **Query & availability** — [Query & Check](https://laravel-zap.com/docs/guides/query-check) (bookable slots, conflicts, schedules by date) - **Guides** — [Real world examples](https://laravel-zap.com/docs/guides/real-world-examples), [Configuration](https://laravel-zap.com/docs/guides/configuration), [Contributing](https://laravel-zap.com/docs/guides/contributing), [AI agent support](https://laravel-zap.com/docs/guides/ai-agent-support) ::card-grid :::card --- icon: i-heroicons-rocket-launch title: Introduction to: https://laravel-zap.com/docs/getting-started/introduction --- Core concepts: schedule types, overlap behavior, and how availability, appointments, and blocked times interact. ::: :::card --- icon: i-heroicons-arrow-down-tray title: Installation to: https://laravel-zap.com/docs/getting-started/installation --- Install and configure Zap in your Laravel project. ::: :::card --- icon: i-heroicons-bolt title: Quick Start to: https://laravel-zap.com/docs/getting-started/quick-start --- One end-to-end example: availability + blocked + appointment + bookable slots. Links to deeper sections. ::: #title Get Started :: ::card-grid :::card --- icon: i-heroicons-calendar-days title: Schedule Patterns to: https://laravel-zap.com/docs/guides/schedule-patterns --- Recurrence in one place: daily, weekly, monthly, ordinal weekday (e.g. 1st Wednesday), dynamic intervals. ::: :::card --- icon: i-heroicons-magnifying-glass title: Query & Check to: https://laravel-zap.com/docs/guides/query-check --- Get bookable slots, check if a time is free, list schedules for a date, handle conflicts. ::: :::card --- icon: i-heroicons-beaker title: Real World Examples to: https://laravel-zap.com/docs/guides/real-world-examples --- Practical examples for common scenarios. ::: #title Guides :: # Introduction ## 🎯 What is Zap? A comprehensive calendar and scheduling system for Laravel. Manage **availabilities**, **appointments**, **blocked** times, and **custom** schedules for any resource—doctors, meeting rooms, employees, and more. **Perfect for:** appointment booking systems • resource scheduling • shift management • calendar applications ## 🧩 Core concepts Zap is built around four **schedule types**. Each schedule has one or more **periods** (time slots). **Recurrence** (daily, weekly, monthly, ordinal weekday, etc.) defines when the schedule repeats. **Bookable slots** are computed from availability minus appointments and blocked times. | Type | Purpose | Overlap Behavior | | ---------------- | ----------------------------- | ----------------------- | | **Availability** | When a resource can be booked | ✅ Allows overlaps | | **Appointment** | Bookings / scheduled events | ❌ Exclusive | | **Blocked** | When booking is forbidden | ❌ Exclusive | | **Custom** | Your rules (overlap, etc.) | ⚙️ You define the rules | ## How it fits together - **Availability** defines when a resource *can* be booked. - **Appointments** and **blocked** periods mark when it *cannot* be booked (exclusive — no overlaps allowed). - To get **bookable slots** for a date, Zap uses availability and subtracts appointments and blocked times (see [Query & Check](https://laravel-zap.com/docs/guides/query-check)). - Schedules can be **one-off** (single date) or **recurring** (daily, weekly, monthly, [first/last weekday of month](https://laravel-zap.com/docs/guides/schedule-patterns/ordinal-weekday), etc.). All recurrence is covered in [Schedule patterns](https://laravel-zap.com/docs/guides/schedule-patterns). # Installation **Requirements:** PHP ≥8.5, Laravel ≥12.0 ## Before Running Migrations (UUID/ULID/GUID) If your models use UUID-style primary keys, review the custom model steps in the [Configuration guide](https://laravel-zap.com/docs/guides/configuration#custom-model-support-uuids-ulids-guids) before publishing or running the migrations. ## Composer Install the package via composer: ```bash composer require laraveljutsu/zap ``` ## Setup Publish the migrations and run them: ```bash php artisan vendor:publish --tag=zap-migrations php artisan migrate ``` Add the `HasSchedules` trait to your schedulable models: ```php use Zap\Models\Concerns\HasSchedules; use Illuminate\Database\Eloquent\Model; class Doctor extends Model { use HasSchedules; } ``` # Quick Start One end-to-end example: define availability, block time, create an appointment, then get bookable slots. For recurrence options (daily, weekly, monthly, [ordinal weekday](https://laravel-zap.com/docs/guides/schedule-patterns/ordinal-weekday), etc.), see [Schedule patterns](https://laravel-zap.com/docs/guides/schedule-patterns). For querying and availability, see [Query & Check](https://laravel-zap.com/docs/guides/query-check). ```php use Zap\Facades\Zap; // 1️⃣ Define working hours Zap::for($doctor) ->named('Office Hours') ->availability() ->forYear(2025) ->addPeriod('09:00', '12:00') ->addPeriod('14:00', '17:00') ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->save(); // 2️⃣ Block lunch break Zap::for($doctor) ->named('Lunch Break') ->blocked() ->forYear(2025) ->addPeriod('12:00', '13:00') ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->save(); // 3️⃣ Create an appointment Zap::for($doctor) ->named('Patient A - Consultation') ->appointment() ->from('2025-01-15') ->addPeriod('10:00', '11:00') ->withMetadata(['patient_id' => 1, 'type' => 'consultation']) ->save(); // 4️⃣ Get bookable slots (60 min slots, 15 min buffer) $slots = $doctor->getBookableSlots('2025-01-15', 60, 15); // Returns: [['start_time' => '09:00', 'end_time' => '10:00', 'is_available' => true, ...], ...] // 5️⃣ Find next available slot $nextSlot = $doctor->getNextBookableSlot('2025-01-15', 60, 15); // 6️⃣ Check if a specific time range is bookable $isAvailable = $doctor->isBookableAtTime('2025-01-15', '15:00', '16:00'); // Returns: true or false ``` ## Using the Helper Function Instead of using the `Zap` facade, you can use the global `zap()` helper function. Both approaches are equivalent—the helper just doesn't require importing the facade class. ### Facade vs Helper ```php // Facade (requires import) use Zap\Facades\Zap; Zap::for($doctor)->availability()... // Helper (no import needed) zap()->for($doctor)->availability()... ``` ### Complete Examples with `zap()` Here are the same examples using the helper function: #### Creating Availability Schedules ```php // Define working hours zap()->for($doctor) ->named('Office Hours') ->availability() ->forYear(2025) ->addPeriod('09:00', '12:00') ->addPeriod('14:00', '17:00') ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->save(); // Block lunch break zap()->for($doctor) ->named('Lunch Break') ->blocked() ->forYear(2025) ->addPeriod('12:00', '13:00') ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->save(); ``` #### Creating Appointments ```php // Create an appointment zap()->for($doctor) ->named('Patient A - Consultation') ->appointment() ->from('2025-01-15') ->addPeriod('10:00', '11:00') ->withMetadata(['patient_id' => 1, 'type' => 'consultation']) ->save(); ``` #### Checking for Conflicts ```php // Create a schedule and check for conflicts $schedule = zap()->for($doctor) ->named('Patient B - Follow-up') ->appointment() ->from('2025-01-15') ->addPeriod('10:00', '11:00') ->save(); // Check if the schedule has conflicts $hasConflicts = zap()->hasConflicts($schedule); $conflicts = zap()->findConflicts($schedule); if (!$hasConflicts) { // Schedule created successfully, no conflicts } else { // Handle conflicts - $conflicts contains overlapping schedules } ``` **Note:** Both the facade and helper function are equivalent. Choose whichever approach fits your coding style or project conventions. # Schedule Patterns Zap supports flexible recurrence patterns: daily, weekly (including odd/even weeks), biweekly, **dynamic weekly** (every 3–52 weeks), monthly, bimonthly, quarterly, semi-annual, annual, **monthly ordinal weekday** (e.g. 1st Wednesday or last Monday of the month), and **dynamic monthly** (every 4, 5, 7–11 months). You also define **date ranges** and **time periods** for each schedule. ## Recurrence at a glance | Pattern | Methods | Description | | -------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------- | | Daily | `daily()` | Every day | | Weekly | `weekly()`, `weekDays()` | Every week on given weekdays | | Weekly odd/even | `weeklyOdd()`, `weeklyEven()`, `weekOddDays()`, `weekEvenDays()` | Every week on odd/even ISO weeks | | Biweekly | `biweekly()` | Every 2 weeks | | Every N weeks (3–52) | `everyThreeWeeks()`, `everyFourWeeks()`, … `everyFiftyTwoWeeks()` | Every 3, 4, … 52 weeks on given days | | Monthly | `monthly()` | Every month on given day(s) | | Bimonthly / Quarterly / Semi-annual / Annual | `bimonthly()`, `quarterly()`, `semiannually()`, `annually()` | Every 2, 3, 6, 12 months | | **Monthly ordinal weekday** | `firstWednesdayOfMonth()`, `secondFridayOfMonth()`, `lastMondayOfMonth()`, … | 1st, 2nd, 3rd, 4th, or last weekday of the month *(added 2025)* | | Every N months (4, 5, 7–11) | `everyFourMonths()`, `everyFiveMonths()`, … `everyElevenMonths()` | Every 4, 5, 7, 8, 9, 10, or 11 months | Use the sidebar to jump to a specific pattern or topic. # Daily & Weekly ## Daily Schedule runs every day within your date range. ```php $schedule = Zap::for($doctor) ->named('Office Hours') ->availability(); $schedule->daily() ->from('2025-01-01') ->to('2025-12-31'); ``` ## Weekly (specific days) Use `weekly()` with an array of weekday names. Add `addPeriod()` for time slots and `from()/to()` or `forYear()` for the validity range. ```php $schedule->weekly(['monday', 'wednesday', 'friday'])->forYear(2025); ``` ## weekDays() — weekly + single time period `weekDays()` combines weekly recurrence with one time period in a single call. Useful for office hours or regular shifts. ```php // Using weekDays() - combines weekly() and addPeriod() Zap::for($doctor) ->named('Office Hours') ->availability() ->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '09:00', '17:00') ->forYear(2025) ->save(); // Equivalent to: Zap::for($doctor) ->named('Office Hours') ->availability() ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('09:00', '17:00') ->forYear(2025) ->save(); ``` ::tip Use `weekDays()` when you have a single time period for specific weekdays. For multiple time periods (e.g., split shifts), use `weekly()` with multiple `addPeriod()` calls instead. :: # Weekly Odd / Even Schedule only on **odd-numbered** or **even-numbered** ISO weeks. Useful for alternating schedules (e.g. shared resources, rotating shifts). ## weeklyOdd() and weeklyEven() ```php // Weekly odd - runs only on odd-numbered ISO weeks Zap::for($employee) ->named('Morning Shift - Odd Weeks') ->availability() ->weeklyOdd(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('05:00', '13:00') ->forYear(2025) ->save(); // Weekly even - runs only on even-numbered ISO weeks Zap::for($employee) ->named('Afternoon Shift - Even Weeks') ->availability() ->weeklyEven(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('13:00', '21:00') ->forYear(2025) ->save(); ``` ## weekOddDays() and weekEvenDays() Convenience methods that combine odd/even weekly recurrence with a single time period (like `weekDays()` for regular weekly). ```php // Using weekOddDays() - combines weeklyOdd() and addPeriod() Zap::for($employee) ->named('Morning Shift - Odd Weeks') ->availability() ->weekOddDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '05:00', '13:00') ->forYear(2025) ->save(); // Using weekEvenDays() - combines weeklyEven() and addPeriod() Zap::for($employee) ->named('Afternoon Shift - Even Weeks') ->availability() ->weekEvenDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '13:00', '21:00') ->forYear(2025) ->save(); ``` ::tip Weekly odd/even scheduling uses **ISO week numbers** . Week 1 is the first week of the year that contains a Thursday. This ensures consistent behavior across different calendar systems. :: # Biweekly Schedule runs **every 2 weeks** on the given weekdays. The week of the schedule’s start date (or the optional anchor) is the first occurrence. ```php $schedule = Zap::for($doctor) ->named('Office Hours') ->availability(); // Bi-weekly (week of the start date by default, optional anchor) $schedule->biweekly(['tuesday', 'thursday']) ->from('2025-01-07') ->to('2025-03-31'); ``` Use `from()/to()` or `forYear()` and `addPeriod()` as needed, then `save()`. For intervals of **3 to 52 weeks**, use the dynamic weekly methods: [Every X weeks](https://laravel-zap.com/docs/guides/schedule-patterns/dynamic-weekly) (`everyThreeWeeks`, `everyFourWeeks`, … `everyFiftyTwoWeeks`). # Dynamic Weekly (Every X Weeks) For intervals of **3 to 52 weeks**, use `everyThreeWeeks`, `everyFourWeeks`, … through `everyFiftyTwoWeeks`. These are distinct from `weekly()` (every week) and [biweekly()](https://laravel-zap.com/docs/guides/schedule-patterns/biweekly) (every 2 weeks). ## Signature `everyXWeeks(array $days, CarbonInterface|string|null $startsOn = null)` - **$days:** weekday names, e.g. `['monday', 'friday']` - **$startsOn:** optional anchor date (the week of this date is the first occurrence); if omitted, the schedule’s start date is used Same builder chain as other recurrences: add `->from(...)->to(...)` or `->forYear(...)`, `->addPeriod(...)` as needed, then `->save()`. ## Examples ```php // Every 3 weeks on Tuesday and Thursday Zap::for($resource) ->named('Review Sessions') ->availability() ->everyThreeWeeks(['tuesday', 'thursday']) ->from('2025-01-01') ->to('2025-12-31') ->addPeriod('14:00', '16:00') ->save(); // Every 4 weeks with explicit startsOn anchor (first occurrence is the week of 2025-01-06) Zap::for($resource) ->named('Monthly Sync') ->availability() ->everyFourWeeks(['monday'], '2025-01-06') ->forYear(2025) ->addPeriod('10:00', '11:00') ->save(); // Every 6 weeks on Wednesdays Zap::for($resource) ->named('Bi-monthly Check-in') ->availability() ->everySixWeeks(['wednesday']) ->forYear(2025) ->addPeriod('09:00', '10:00') ->save(); ``` # Monthly, Bimonthly & Quarterly These methods run on specific **day(s) of the month** at monthly, every-2-months, quarterly, semi-annual, or annual intervals. All support multiple days and an optional `start_month` anchor. ## monthly() Every month on the given day(s). ```php $schedule->monthly(['days_of_month' => [1, 15]])->forYear(2025); ``` ## bimonthly() Every 2 months. Optional `start_month` (1–12) anchors the first occurrence. ```php $schedule->bimonthly(['days_of_month' => [5, 20], 'start_month' => 2]) ->from('2025-01-05') ->to('2025-06-30'); ``` ## quarterly() Every 3 months. Optional `start_month` anchors the first occurrence. ```php $schedule->quarterly(['days_of_month' => [7, 21], 'start_month' => 2]) ->from('2025-02-15') ->to('2025-11-15'); ``` ## semiannually() Every 6 months. Optional `start_month` anchors the first occurrence. ```php $schedule->semiannually(['days_of_month' => [10], 'start_month' => 3]) ->from('2025-03-10') ->to('2025-12-10'); ``` ## annually() Every 12 months. Optional `start_month` anchors the first occurrence. ```php $schedule->annually(['days_of_month' => [1, 15], 'start_month' => 4]) ->from('2025-04-01') ->to('2026-04-01'); ``` ## Config keys - **day\_of\_month** (int): single day, e.g. `15` - **days\_of\_month** (int [] ): multiple days, e.g. `[1, 15]` - **start\_month** (int, 1–12): optional anchor month For **first/second/third/fourth/last weekday of the month** (e.g. “every 1st Wednesday”), use [Monthly ordinal weekday](https://laravel-zap.com/docs/guides/schedule-patterns/ordinal-weekday). For intervals of **4, 5, 7, 8, 9, 10, or 11 months**, use [Dynamic monthly (every X months)](https://laravel-zap.com/docs/guides/schedule-patterns/dynamic-monthly). # Monthly Ordinal Weekday Recurring schedules can be defined by **which weekday of the month** (1st, 2nd, 3rd, 4th, or last), not only by day-of-month numbers. This is useful for meetings like “every first Wednesday” or “every last Monday.” ## API Use fluent methods on the schedule builder. Ordinals: `firstXOfMonth()`, `secondXOfMonth()`, `thirdXOfMonth()`, `fourthXOfMonth()`, `lastXOfMonth()`. Replace **X** with any weekday: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, or Saturday. That gives **35 method names** (5 ordinals × 7 days). Examples: `firstWednesdayOfMonth()`, `secondFridayOfMonth()`, `lastMondayOfMonth()`. ## Examples ```php // Every 1st Wednesday of the month Zap::for($resource)->firstWednesdayOfMonth()->forYear(2025)->addPeriod('09:00', '10:00')->save(); // Every 2nd Friday of the month Zap::for($resource)->secondFridayOfMonth()->forYear(2025)->addPeriod('14:00', '15:00')->save(); // Every last Monday of the month Zap::for($resource)->lastMondayOfMonth()->forYear(2025)->addPeriod('16:00', '17:00')->save(); ``` With a full chain (e.g. availability or appointment): ```php // Monthly standup — 1st Wednesday Zap::for($room) ->named('Monthly Standup') ->appointment() ->firstWednesdayOfMonth() ->forYear(2025) ->addPeriod('09:00', '10:00') ->save(); // Month-end retro — last Monday Zap::for($room) ->named('Month-End Retro') ->appointment() ->lastMondayOfMonth() ->forYear(2025) ->addPeriod('16:00', '17:00') ->save(); // Bi-weekly-style meeting — 2nd Friday Zap::for($resource) ->named('Bi-Monthly Review') ->appointment() ->secondFridayOfMonth() ->forYear(2025) ->addPeriod('14:00', '15:00') ->save(); ``` ## Use cases | Use case | Example | | ----------------------------------- | -------------------------- | | Monthly standup | 1st Wednesday of the month | | Month-end review / retro | Last Monday or last Friday | | Recurring review (bi-monthly style) | 2nd Friday of the month | | Board meeting | 3rd Tuesday of the month | | Weekly-style but once per month | 4th Thursday of the month | ## Technical detail (reference) - Stored as frequency `monthly_ordinal_weekday` with config: `ordinal` (1–5, 5 = last) and `day_of_week` (0–6). - Works with conflict detection, bookable slots, and schedule queries like other recurring patterns. For monthly by **day-of-month** (e.g. 1st and 15th), see [Monthly, Bimonthly & Quarterly](https://laravel-zap.com/docs/guides/schedule-patterns/monthly-quarterly). For every N months, see [Dynamic monthly](https://laravel-zap.com/docs/guides/schedule-patterns/dynamic-monthly). # Dynamic Monthly (Every X Months) For intervals of **4, 5, 7, 8, 9, 10, or 11 months**, use `everyFourMonths`, `everyFiveMonths`, `everySevenMonths`, `everyEightMonths`, `everyNineMonths`, `everyTenMonths`, or `everyElevenMonths`. Intervals 1–3, 6, and 12 are covered by [monthly(), bimonthly(), quarterly(), semiannually(), annually()](https://laravel-zap.com/docs/guides/schedule-patterns/monthly-quarterly). ## Signature `everyXMonths(array $config = [])` **Config keys:** - **day\_of\_month** (int): single day, e.g. `15` - **days\_of\_month** (int [] ): multiple days, e.g. `[1, 15]` - **start\_month** (int, 1–12): optional anchor month Same builder chain: use `->forYear(...)` or `->from(...)->to(...)` as needed, then `->save()`. ## Examples ```php // Every 4 months on the 15th Zap::for($resource) ->named('Quarterly+') ->availability() ->everyFourMonths(['day_of_month' => 15]) ->forYear(2025) ->addPeriod('09:00', '17:00') ->save(); // Every 5 months on the 1st and 15th, starting from February Zap::for($resource) ->named('Multi-day Every 5 Months') ->availability() ->everyFiveMonths(['days_of_month' => [1, 15], 'start_month' => 2]) ->forYear(2025) ->addPeriod('10:00', '12:00') ->save(); // Every 7 months on the 10th Zap::for($resource) ->named('Twice a Year+') ->availability() ->everySevenMonths(['day_of_month' => 10]) ->forYear(2025) ->addPeriod('14:00', '15:00') ->save(); ``` # Date Ranges Define the **validity period** of a schedule: when it starts and (optionally) when it ends. ```php $schedule = Zap::for($doctor) ->named('Office Hours') ->availability(); // Single date (no end) $schedule->from('2025-01-15'); // Alternative: on() is an alias for from() $schedule->on('2025-01-15'); // Date range $schedule->from('2025-01-01')->to('2025-12-31'); // Alternative syntax $schedule->between('2025-01-01', '2025-12-31'); // Entire year shortcut $schedule->forYear(2025); ``` Use these after choosing a recurrence (e.g. `weekly()`, `monthly()`) and before or after `addPeriod()`, then call `save()`. # Time Periods Define the **time slots** within a day. Use one or more `addPeriod()` calls after setting recurrence and date range. ```php $schedule = Zap::for($doctor) ->named('Office Hours') ->availability(); // Single period $schedule->addPeriod('09:00', '17:00'); // Multiple periods (split shifts) $schedule->addPeriod('09:00', '12:00'); $schedule->addPeriod('14:00', '17:00'); ``` Times are in 24-hour format. Combine with any recurrence (e.g. [Daily & Weekly](https://laravel-zap.com/docs/guides/schedule-patterns/daily-weekly), [Monthly](https://laravel-zap.com/docs/guides/schedule-patterns/monthly-quarterly)) and [Date Ranges](https://laravel-zap.com/docs/guides/schedule-patterns/date-ranges), then `save()`. # Query & Check This page answers: **How do I get bookable slots?** **How do I check if a time is free?** **How do I list schedules for a date?** For defining when a resource can be booked, see [Schedule patterns](https://laravel-zap.com/docs/guides/schedule-patterns) and [Quick start](https://laravel-zap.com/docs/getting-started/quick-start). ## I want to… | Goal | Method / API | | --------------------------------------------------- | ------------------------------------------------------------------------------------------- | | Know if there is any bookable slot on a date | `$model->isBookableAt('2025-01-15', 60)` or `isBookableAt('date', duration, bufferMinutes)` | | Check if a specific time range is free | `$model->isBookableAtTime('2025-01-15', '09:00', '10:00', null, 60, 15)` | | List all bookable slots for a date | `$model->getBookableSlots('2025-01-15', 60, 15)` | | Find the next available slot | `$model->getNextBookableSlot('2025-01-15', 60, 15)` | | List schedules on a date | `$model->schedulesForDate('2025-01-15')->get()` | | List schedules in a date range | `$model->schedulesForDateRange('2025-01-01', '2025-01-31')->get()` | | Check for overlapping schedules (conflicts) | `Zap::findConflicts($schedule)` / `Zap::hasConflicts($schedule)` | | Filter by type (appointment, availability, blocked) | `$model->appointmentSchedules()`, `availabilitySchedules()`, `blockedSchedules()` | ::warning **Deprecation:** `isAvailableAt()` is deprecated. Use `isBookableAt()` , `isBookableAtTime()` , or `getBookableSlots()` for all new code. :: ## Check availability (bookable slots) ```php // Check if there is at least one bookable slot on the day $isBookable = $doctor->isBookableAt('2025-01-15', 60); // With buffer between slots (e.g. 15 minutes) $isBookable = $doctor->isBookableAt('2025-01-15', 60, 15); // Check if a specific time range is bookable $isBookable = $doctor->isBookableAtTime('2025-01-15', '09:00', '10:00'); // Get all bookable slots (date, slot duration in minutes, buffer in minutes) $slots = $doctor->getBookableSlots('2025-01-15', 60, 15); // Find the next available slot (from date, duration, optional buffer) $nextSlot = $doctor->getNextBookableSlot('2025-01-15', 60, 15); ``` ## Check if a specific time range is bookable The `isBookableAtTime()` method checks whether a specific time range is available for booking. ```php public function isBookableAtTime( string $date, string $startTime, string $endTime, ?Collection $schedules = null, int $slotDuration = 60, ?int $bufferMinutes = null ): bool ``` | Parameter | Type | Default | Description | | ---------------- | ---------------- | -------- | -------------------------------------- | | `$date` | string | required | The date to check (Y-m-d format) | | `$startTime` | string | required | Start time (H\:i format) | | `$endTime` | string | required | End time (H\:i format) | | `$schedules` | Collection\|null | null | Pre-loaded schedules (for performance) | | `$slotDuration` | int | 60 | Slot granularity in minutes | | `$bufferMinutes` | int\|null | null | Buffer between slots in minutes | ### Usage examples ```php // Check if 09:00-09:30 is bookable using default 60-minute slots $user->isBookableAtTime('2025-01-06', '09:00', '09:30'); // Check if 09:30-10:00 is bookable using 30-minute slots // Useful when your booking system allows finer granularity $user->isBookableAtTime('2025-01-06', '09:30', '10:00', null, 30); // Check if a 30-min slot at 09:45 is available, with a 15-min buffer enforced $bookable = $user->isBookableAtTime('2025-01-15', '09:45', '10:15', null, 30, 15); // With preloaded schedules and custom slot duration $schedules = $user->schedules()->active()->forDate('2025-01-06')->get(); $user->isBookableAtTime('2025-01-06', '09:30', '10:00', $schedules, 30); ``` ::tip Pass the same `$bufferMinutes` value you use with `getBookableSlots()` to ensure both calls evaluate availability under identical conditions. Mismatched buffer values can cause `isBookableAtTime()` to accept a slot that `getBookableSlots()` would never generate. :: ### Understanding slot duration By default, `isBookableAtTime()` generates 60-minute slots to check availability. If your application allows shorter bookings (e.g., 15 or 30-minute appointments), pass a custom `$slotDuration` to ensure the requested time range aligns with valid slot boundaries. For example, with 60-minute slots starting at 09:00, a request for 09:30-10:00 would fail because it doesn't align with slot boundaries. Using `$slotDuration = 30` generates 30-minute slots (09:00-09:30, 09:30-10:00, etc.), making the 09:30-10:00 request valid. ## Conflicts ```php $conflicts = Zap::findConflicts($schedule); $hasConflicts = Zap::hasConflicts($schedule); ``` ## Retrieve schedules ```php // By date or range $doctor->schedulesForDate('2025-01-15')->get(); $doctor->schedulesForDateRange('2025-01-01', '2025-01-31')->get(); // By type: appointment, availability, blocked $doctor->appointmentSchedules()->get(); $doctor->availabilitySchedules()->get(); $doctor->blockedSchedules()->get(); ``` ## Inspect a schedule ```php $schedule->isAvailability(); $schedule->isAppointment(); $schedule->isBlocked(); ``` # Real-World Examples ## 🏥 Doctor Appointment System ```php // Office hours Zap::for($doctor) ->named('Office Hours') ->availability() ->forYear(2025) ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('09:00', '12:00') ->addPeriod('14:00', '17:00') ->save(); // Lunch break Zap::for($doctor) ->named('Lunch Break') ->blocked() ->forYear(2025) ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('12:00', '13:00') ->save(); // Book appointment Zap::for($doctor) ->named('Patient A - Checkup') ->appointment() ->from('2025-01-15') ->addPeriod('10:00', '11:00') ->withMetadata(['patient_id' => 1]) ->save(); // Get available slots $slots = $doctor->getBookableSlots('2025-01-15', 60, 15); ``` ## 🏢 Meeting Room Booking ```php // Room availability (using weekDays convenience method) Zap::for($room) ->named('Conference Room A') ->availability() ->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '08:00', '18:00') ->forYear(2025) ->save(); // Book meeting Zap::for($room) ->named('Board Meeting') ->appointment() ->from('2025-03-15') ->addPeriod('09:00', '11:00') ->withMetadata(['organizer' => 'john@company.com']) ->save(); ``` ## 👔 Employee Shift Management ```php // Regular schedule (using weekDays convenience method) Zap::for($employee) ->named('Regular Shift') ->availability() ->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '09:00', '17:00') ->forYear(2025) ->save(); // Vacation Zap::for($employee) ->named('Vacation Leave') ->blocked() ->between('2025-06-01', '2025-06-15') ->addPeriod('00:00', '23:59') ->save(); ``` ## 🔄 Alternating Weekly Schedules ### Rotating Shift Schedule Perfect for employees who alternate between morning and afternoon shifts every other week: ```php // Employee works morning shift (5:00-13:00) on odd weeks Zap::for($employee) ->named('Morning Shift - Odd Weeks') ->availability() ->weekOddDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '05:00', '13:00') ->forYear(2025) ->save(); // Same employee works afternoon shift (13:00-21:00) on even weeks Zap::for($employee) ->named('Afternoon Shift - Even Weeks') ->availability() ->weekEvenDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '13:00', '21:00') ->forYear(2025) ->save(); ``` ### Shared Office Space When two people share an office and need to alternate weeks: ```php // Person A uses the office on odd weeks Zap::for($personA) ->named('Office Access - Odd Weeks') ->availability() ->weeklyOdd(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('09:00', '17:00') ->forYear(2025) ->save(); // Person B uses the office on even weeks Zap::for($personB) ->named('Office Access - Even Weeks') ->availability() ->weeklyEven(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->addPeriod('09:00', '17:00') ->forYear(2025) ->save(); ``` ## 📅 Extended Recurring Frequencies ### Bi-Weekly Team Meetings ```php // Bi-weekly team standup every other Monday Zap::for($team) ->named('Team Standup') ->appointment() ->biweekly(['monday']) ->from('2025-01-06') ->to('2025-12-31') ->addPeriod('09:00', '09:30') ->save(); ``` ### Quarterly Reviews ```php // Quarterly performance reviews on the 15th of every quarter Zap::for($manager) ->named('Quarterly Reviews') ->blocked() ->quarterly(['days_of_month' => [15], 'start_month' => 1]) ->from('2025-01-15') ->to('2025-12-15') ->addPeriod('10:00', '17:00') ->save(); ``` ### Monthly Standup & Month-End (Ordinal Weekday) For “which weekday of the month” (e.g. 1st Wednesday, last Monday), use [Monthly ordinal weekday](https://laravel-zap.com/docs/guides/schedule-patterns/ordinal-weekday): ```php // Monthly standup — every 1st Wednesday Zap::for($room) ->named('Monthly Standup') ->appointment() ->firstWednesdayOfMonth() ->forYear(2025) ->addPeriod('09:00', '10:00') ->save(); // Month-end retro — every last Monday Zap::for($room) ->named('Month-End Retro') ->appointment() ->lastMondayOfMonth() ->forYear(2025) ->addPeriod('16:00', '17:00') ->save(); ``` ### Monthly Payroll Processing ```php // Monthly payroll on the 1st and 15th of each month Zap::for($accountant) ->named('Payroll Processing') ->blocked() ->monthly(['days_of_month' => [1, 15]]) ->forYear(2025) ->addPeriod('08:00', '12:00') ->save(); ``` ### Annual Events ```php // Annual company meeting on April 1st and 15th Zap::for($company) ->named('Annual Company Meeting') ->blocked() ->annually(['days_of_month' => [1, 15], 'start_month' => 4]) ->from('2025-04-01') ->to('2026-04-15') ->addPeriod('09:00', '17:00') ->save(); ``` ## 💡 Filter schedules by metadata (tip) If you store identifiers in `metadata`, you can fetch schedules that match them directly through the relation: ```php // Example: fetch all schedules for a given customer id $schedules = $schedulable ->schedules() ->where('metadata->customer_id', $customerId) ->get(); ``` This works for any metadata keys you persist via `->withMetadata([...])`. # Configuration Publish and customize the configuration file: ```bash php artisan vendor:publish --tag=zap-config ``` Key settings in `config/zap.php`: ```php use Carbon\CarbonInterface; return [ 'calendar' => [ 'week_start' => CarbonInterface::MONDAY, // Week start day for bi-weekly calculations ], 'time_slots' => [ 'buffer_minutes' => 0, // Default buffer between slots ], 'default_rules' => [ 'no_overlap' => [ 'enabled' => true, 'applies_to' => ['appointment', 'blocked'], ], ], ]; ``` # Custom Model Support (UUIDs, ULIDs, GUIDs) Zap assumes auto-incrementing integers by default. If you use UUID-style keys, customize the models and migrations **before** running them. ### 1) Extend the models Create your own models that add Laravel's `HasUuids` trait: ```php use Illuminate\Database\Eloquent\Concerns\HasUuids; use Zap\Models\Schedule as BaseSchedule; use Zap\Models\SchedulePeriod as BaseSchedulePeriod; class Schedule extends BaseSchedule { use HasUuids; } class SchedulePeriod extends BaseSchedulePeriod { use HasUuids; } ``` ### 2) Update your schedulable model ```php use Illuminate\Database\Eloquent\Concerns\HasUuids; use Zap\Models\Concerns\HasSchedules; class Doctor extends Model { use HasSchedules, HasUuids; } ``` ### 3) Point config to your models ```php // config/zap.php 'models' => [ 'schedule' => \App\Models\Schedule::class, 'schedule_period' => \App\Models\SchedulePeriod::class, ], ``` ### 4) Update published migrations ```diff // database/migrations/**_create_schedules_table.php - $table->id(); - $table->morphs('schedulable'); + $table->uuid('id')->primary(); + $table->uuidMorphs('schedulable'); // database/migrations/**_create_schedule_periods_table.php - $table->id(); - $table->foreignId('schedule_id')->constrained()->cascadeOnDelete(); + $table->uuid('id')->primary(); + $table->foreignUuid('schedule_id')->constrained()->cascadeOnDelete(); ``` Adjust to `ulid`/`guid` helpers if you prefer those types. Make these changes before migrating so keys stay consistent across your app. # Advanced Features ## Custom Schedules with Explicit Rules ```php Zap::for($user) ->named('Custom Event') ->custom() ->from('2025-01-15') ->addPeriod('15:00', '16:00') ->noOverlap() // Explicitly prevent overlaps ->save(); ``` ## Metadata Support Attach arbitrary data to any schedule: ```php ->withMetadata([ 'patient_id' => 1, 'type' => 'consultation', 'notes' => 'Follow-up required' ]) ``` # 🤝 Contributing We welcome contributions! Follow PSR-12 coding standards and include tests. ```bash git clone https://github.com/ludoguenet/laravel-zap.git cd laravel-zap composer install vendor/bin/pest ``` # 📄 License Laravel Zap is open-source software licensed under the **MIT License**. See the `LICENSE` file in the package repository for full details. # 🔒 Security Report vulnerabilities to ****. Please do **not** use the public issue tracker for security reports. # ❤️ Credits Made with ❤️ by [Laravel Jutsu](https://www.youtube.com/@LaravelJutsu){rel=""nofollow""} for the Laravel community. # AI Agent Support ## 🤖 Laravel Boost 2.0 Skills Laravel Zap ships with native [Laravel Boost](https://laravel.com/ai/boost){rel=""nofollow""} 2.0 support. When both packages are installed, Boost automatically discovers Zap's skills, enabling AI agents to generate accurate scheduling code with full knowledge of the API. **Zero configuration required** — just install both packages and your AI agent automatically gains context about Laravel Zap's scheduling API. ## 📦 Included Skills Zap provides three skills located in `resources/boost/skills/`: | Skill | Description | | -------------------- | --------------------------------------------------------------------------------------------------------------------- | | **zap-schedules** | Schedule types (availability, appointment, blocked, custom), fluent builder API, validation rules, conflict detection | | **zap-availability** | Bookable slots, availability checks, querying schedules | | **zap-recurrence** | Daily, weekly, odd/even weeks, biweekly, monthly, quarterly, semi-annually, annually | ## 🚀 How It Works Laravel Boost 2.0 introduced "Skills" — a way for packages to ship AI-contextual documentation that agents auto-discover. When users have both Laravel Boost and Laravel Zap installed: 1. Boost scans installed packages for skill definitions 2. Zap's skills are automatically loaded 3. AI agents receive accurate, package-specific context 4. Code generation becomes more reliable and API-aware ```php // Your AI agent now understands Zap's full API // and can generate correct scheduling code like: Zap::for($doctor) ->named('Office Hours') ->availability() ->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '09:00', '17:00') ->forYear(2025) ->save(); ``` ## ✅ Requirements - Laravel Zap (any version) - Laravel Boost 2.0 or higher ::tip Skills are read-only documentation that help AI agents understand your codebase. They don't execute code or modify your application — they simply provide context for better code generation. :: # Install ```bash [composer] composer require laraveljutsu/zap ``` # Quickstart ```php [quick-start.php] use Zap\Facades\Zap; Zap::for($doctor) ->named('Office Hours') ->availability() ->forYear(2025) ->addPeriod('09:00', '12:00') ->addPeriod('14:00', '17:00') ->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) ->save(); // Get bookable slots $slots = $doctor->getBookableSlots('2025-01-15', 60, 15); // Check if a specific time range is bookable $isAvailable = $doctor->isBookableAtTime('2025-01-15', '15:00', '16:00'); ``` # Laravel Zap - Flexible Schedule and Calendar Management :landing-laravel-jutsu-banner :landing-hero :landing-core-concepts :landing-features :landing-real-world-examples :landing-call-to-action