Skip to content

Commit d83f416

Browse files
committed
wip: schedule period eloquent builder
1 parent ee83abd commit d83f416

File tree

2 files changed

+110
-86
lines changed

2 files changed

+110
-86
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace Zap\Models\Builders;
4+
5+
use Carbon\Carbon;
6+
use Carbon\CarbonInterface;
7+
use Illuminate\Database\Connection;
8+
use Illuminate\Database\Eloquent\Builder;
9+
use PDO;
10+
11+
class SchedulePeriodBuilder extends Builder
12+
{
13+
/**
14+
* Scope a query to only include available periods.
15+
*/
16+
public function available(): SchedulePeriodBuilder
17+
{
18+
return $this->where('is_available', true);
19+
}
20+
21+
/**
22+
* Scope a query to only include periods for a specific date.
23+
*/
24+
public function forDate(string $date): SchedulePeriodBuilder
25+
{
26+
return $this->where('date', Carbon::parse($date));
27+
}
28+
29+
/**
30+
* Scope a query to only include periods within a time range.
31+
*/
32+
public function forTimeRange(string $startTime, string $endTime): SchedulePeriodBuilder
33+
{
34+
return $this->where('start_time', '>=', $startTime)
35+
->where('end_time', '<=', $endTime);
36+
}
37+
38+
/**
39+
* Scope a query to find overlapping periods.
40+
*/
41+
public function overlapping(string $date, string $startTime, string $endTime, ?CarbonInterface $endDate = null): SchedulePeriodBuilder
42+
{
43+
// Normalize input times to HH:MM format
44+
$startTime = str_pad($startTime, 5, '0', STR_PAD_LEFT);
45+
$endTime = str_pad($endTime, 5, '0', STR_PAD_LEFT);
46+
47+
// Apply date filter
48+
$this->when(is_null($endDate), fn ($q) => $q->whereDate('date', $date));
49+
50+
// Apply time overlap logic based on database driver
51+
52+
/** @var Connection $connection */
53+
$connection = $this->getConnection();
54+
$driver = $connection->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME);
55+
56+
if ($driver === 'sqlite') {
57+
return $this->applySqliteTimeOverlap($this, $startTime, $endTime);
58+
}
59+
60+
if ($driver === 'pgsql') {
61+
return $this->applyPostgresTimeOverlap($this, $startTime, $endTime);
62+
}
63+
64+
return $this->applyStandardTimeOverlap($this, $startTime, $endTime);
65+
}
66+
67+
/**
68+
* Apply SQLite-specific time overlap conditions.
69+
*/
70+
private function applySqliteTimeOverlap(SchedulePeriodBuilder $query, string $startTime, string $endTime): SchedulePeriodBuilder
71+
{
72+
return $query
73+
->whereRaw('CASE WHEN LENGTH(start_time) = 4 THEN "0" || start_time ELSE start_time END < ?', [$endTime])
74+
->whereRaw('CASE WHEN LENGTH(end_time) = 4 THEN "0" || end_time ELSE end_time END > ?', [$startTime]);
75+
}
76+
77+
/**
78+
* Apply standard SQL time overlap conditions (MySQL).
79+
*/
80+
private function applyStandardTimeOverlap(SchedulePeriodBuilder $query, string $startTime, string $endTime): SchedulePeriodBuilder
81+
{
82+
return $query
83+
->whereRaw("LPAD(start_time, 5, '0') < ?", [$endTime])
84+
->whereRaw("LPAD(end_time, 5, '0') > ?", [$startTime]);
85+
}
86+
87+
/**
88+
* Apply PostgreSQL-specific time overlap conditions.
89+
*/
90+
private function applyPostgresTimeOverlap(SchedulePeriodBuilder $query, string $startTime, string $endTime): SchedulePeriodBuilder
91+
{
92+
return $query
93+
->whereRaw('LPAD(start_time::text, 5, \'0\') < ?', [$endTime])
94+
->whereRaw('LPAD(end_time::text, 5, \'0\') > ?', [$startTime]);
95+
}
96+
}

src/Models/SchedulePeriod.php

Lines changed: 14 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44

55
use Carbon\Carbon;
66
use Carbon\CarbonInterface;
7-
use Illuminate\Database\Connection;
87
use Illuminate\Database\Eloquent\Model;
98
use Illuminate\Database\Eloquent\Relations\BelongsTo;
109
use Illuminate\Support\Facades\Date;
11-
use PDO;
10+
use Zap\Models\Builders\SchedulePeriodBuilder;
1211

1312
/**
1413
* @property int|string $id
@@ -24,6 +23,11 @@
2423
* @property-read int $duration_minutes
2524
* @property-read Carbon $start_date_time
2625
* @property-read Carbon $end_date_time
26+
*
27+
* @method static \Illuminate\Database\Eloquent\Builder available()
28+
* @method static \Illuminate\Database\Eloquent\Builder forDate(string $date)
29+
* @method static \Illuminate\Database\Eloquent\Builder forTimeRange(string $startTime, string $endTime)
30+
* @method static \Illuminate\Database\Eloquent\Builder overlapping(string $date, string $startTime, string $endTime, ?CarbonInterface $endDate = null)
2731
*/
2832
class SchedulePeriod extends Model
2933
{
@@ -73,6 +77,14 @@ public function schedule(): BelongsTo
7377
return $this->belongsTo($this->getScheduleClass(), 'schedule_id');
7478
}
7579

80+
/**
81+
* Create a new Eloquent query builder for the model.
82+
*/
83+
public function newEloquentBuilder($query): SchedulePeriodBuilder
84+
{
85+
return new SchedulePeriodBuilder($query);
86+
}
87+
7688
/**
7789
* Get the duration in minutes.
7890
*/
@@ -130,60 +142,6 @@ public function isActiveNow(): bool
130142
return $now->between($startDateTime, $endDateTime);
131143
}
132144

133-
/**
134-
* Scope a query to only include available periods.
135-
*/
136-
public function scopeAvailable(\Illuminate\Database\Eloquent\Builder $query): \Illuminate\Database\Eloquent\Builder
137-
{
138-
return $query->where('is_available', true);
139-
}
140-
141-
/**
142-
* Scope a query to only include periods for a specific date.
143-
*/
144-
public function scopeForDate(\Illuminate\Database\Eloquent\Builder $query, string $date): \Illuminate\Database\Eloquent\Builder
145-
{
146-
return $query->where('date', Carbon::parse($date));
147-
}
148-
149-
/**
150-
* Scope a query to only include periods within a time range.
151-
*/
152-
public function scopeForTimeRange(\Illuminate\Database\Eloquent\Builder $query, string $startTime, string $endTime): \Illuminate\Database\Eloquent\Builder
153-
{
154-
return $query->where('start_time', '>=', $startTime)
155-
->where('end_time', '<=', $endTime);
156-
}
157-
158-
/**
159-
* Scope a query to find overlapping periods.
160-
*/
161-
public function scopeOverlapping(\Illuminate\Database\Eloquent\Builder $query, string $date, string $startTime, string $endTime, ?CarbonInterface $endDate = null): \Illuminate\Database\Eloquent\Builder
162-
{
163-
// Normalize input times to HH:MM format
164-
$startTime = str_pad($startTime, 5, '0', STR_PAD_LEFT);
165-
$endTime = str_pad($endTime, 5, '0', STR_PAD_LEFT);
166-
167-
// Apply date filter
168-
$query->when(is_null($endDate), fn ($q) => $q->whereDate('date', $date));
169-
170-
// Apply time overlap logic based on database driver
171-
172-
/** @var Connection $connection */
173-
$connection = $query->getConnection();
174-
$driver = $connection->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME);
175-
176-
if ($driver === 'sqlite') {
177-
return $this->applySqliteTimeOverlap($query, $startTime, $endTime);
178-
}
179-
180-
if ($driver === 'pgsql') {
181-
return $this->applyPostgresTimeOverlap($query, $startTime, $endTime);
182-
}
183-
184-
return $this->applyStandardTimeOverlap($query, $startTime, $endTime);
185-
}
186-
187145
/**
188146
* Convert the period to a human-readable string.
189147
*/
@@ -196,34 +154,4 @@ public function __toString(): string
196154
$this->end_time
197155
);
198156
}
199-
200-
/**
201-
* Apply SQLite-specific time overlap conditions.
202-
*/
203-
private function applySqliteTimeOverlap($query, string $startTime, string $endTime)
204-
{
205-
return $query
206-
->whereRaw('CASE WHEN LENGTH(start_time) = 4 THEN "0" || start_time ELSE start_time END < ?', [$endTime])
207-
->whereRaw('CASE WHEN LENGTH(end_time) = 4 THEN "0" || end_time ELSE end_time END > ?', [$startTime]);
208-
}
209-
210-
/**
211-
* Apply standard SQL time overlap conditions (MySQL).
212-
*/
213-
private function applyStandardTimeOverlap($query, string $startTime, string $endTime)
214-
{
215-
return $query
216-
->whereRaw("LPAD(start_time, 5, '0') < ?", [$endTime])
217-
->whereRaw("LPAD(end_time, 5, '0') > ?", [$startTime]);
218-
}
219-
220-
/**
221-
* Apply PostgreSQL-specific time overlap conditions.
222-
*/
223-
private function applyPostgresTimeOverlap($query, string $startTime, string $endTime)
224-
{
225-
return $query
226-
->whereRaw('LPAD(start_time::text, 5, \'0\') < ?', [$endTime])
227-
->whereRaw('LPAD(end_time::text, 5, \'0\') > ?', [$startTime]);
228-
}
229157
}

0 commit comments

Comments
 (0)