Skip to content

Commit f8fbd1e

Browse files
committed
fix: available slots bug; max_duration not taking into account
1 parent 3c40d68 commit f8fbd1e

File tree

5 files changed

+140
-8
lines changed

5 files changed

+140
-8
lines changed

.cursorrules

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Laravel & PHP Guidelines for Cursor AI
2+
3+
## Core Principle
4+
Follow Laravel conventions first. If Laravel has a documented way to do something, use it. Only deviate when you have a clear justification.
5+
6+
## PHP Standards
7+
- Follow PSR-1, PSR-2, and PSR-12
8+
- Use camelCase for non-public-facing strings
9+
- Use short nullable notation: `?string` not `string|null`
10+
- Always specify `void` return types when methods return nothing
11+
- Use typed properties, not docblocks
12+
- Use constructor property promotion when all properties can be promoted
13+
- One trait per line
14+
15+
## Type Declarations & Docblocks
16+
- Use typed properties over docblocks
17+
- Specify return types including `void`
18+
- Use short nullable syntax: `?Type` not `Type|null`
19+
- Always import classnames in docblocks - never use fully qualified names
20+
- Use one-line docblocks when possible: `/** @var string */`
21+
- For iterables, always specify key and value types: `@param array<int, MyObject> $myArray`
22+
- Use array shape notation for fixed keys, put each key on its own line:
23+
```php
24+
/** @return array{
25+
first: SomeClass,
26+
second: SomeClass
27+
} */
28+
```
29+
30+
## Control Flow
31+
- Happy path last: Handle error conditions first, success case last
32+
- Avoid else: Use early returns instead of nested conditions
33+
- Separate conditions: Prefer multiple if statements over compound conditions
34+
- Always use curly brackets even for single statements
35+
- Ternary operators: Each part on own line unless very short
36+
37+
## Laravel Conventions
38+
39+
### Controllers
40+
- Use singular resource names (BookController, UserController)
41+
- Stick to CRUD methods (index, create, store, show, edit, update, destroy)
42+
- Extract new controllers for non-CRUD actions
43+
44+
### Routes
45+
- URLs: kebab-case (/open-source)
46+
- Route names: camelCase (->name('openSource'))
47+
- Parameters: camelCase ({userId})
48+
- Use tuple notation: [Controller::class, 'method']
49+
50+
### Configuration
51+
- Files: kebab-case (pdf-generator.php)
52+
- Keys: snake_case (chrome_path)
53+
- Add service configs to config/services.php, don't create new files
54+
- Use config() helper, avoid env() outside config files
55+
56+
### Artisan Commands
57+
- Names: kebab-case (delete-old-records)
58+
- Always provide feedback ($this->comment('All ok!'))
59+
- Show progress for loops, summary at end
60+
- Put output BEFORE processing item
61+
62+
## Strings & Formatting
63+
- Use string interpolation over concatenation
64+
- Use __() function over @lang for translations
65+
66+
## Enums
67+
- Use PascalCase for enum values
68+
69+
## Comments
70+
- Avoid comments - write expressive code instead
71+
- Single line: // with space after
72+
- Multi-line: /* start with single *
73+
- Refactor comments into descriptive function names
74+
75+
## Whitespace
76+
- Add blank lines between statements for readability
77+
- Exception: sequences of equivalent single-line operations
78+
- No extra empty lines between {} brackets
79+
- Let code "breathe" - avoid cramped formatting
80+
81+
## Validation
82+
- Use array notation for multiple rules: ['email' => ['required', 'email']]
83+
- Custom validation rules use snake_case
84+
85+
## Blade Templates
86+
- Indent with 4 spaces
87+
- No spaces after control structures: @if($condition)
88+
89+
## Authorization
90+
- Policies use camelCase: Gate::define('editPost', ...)
91+
- Use CRUD words, but 'view' instead of 'show'
92+
93+
## API Routing
94+
- Use plural resource names: /books
95+
- Use kebab-case: /error-occurrences
96+
- Limit deep nesting for simplicity
97+
98+
## Naming Conventions Quick Reference
99+
- Classes: PascalCase (UserController, OrderStatus)
100+
- Methods/Variables: camelCase (getUserName, $firstName)
101+
- Routes: kebab-case (/open-source, /user-profile)
102+
- Config files: kebab-case (pdf-generator.php)
103+
- Config keys: snake_case (chrome_path)
104+
- Artisan commands: kebab-case (delete-old-records)
105+
- Controllers: singular resource name + Controller (BookController, UserController)
106+
- Views: camelCase (openSource.blade.php)
107+
- Jobs: action-based (CreateUser, SendEmailNotification)
108+
- Events: tense-based (UserRegistering, UserRegistered)
109+
- Listeners: action + Listener suffix (SendInvitationMailListener)
110+
- Commands: action + Command suffix (PublishScheduledPostsCommand)
111+
- Mailables: purpose + Mail suffix (AccountActivatedMail)
112+
- Resources/Transformers: plural + Resource/Transformer (UsersResource)
113+
- Enums: descriptive name, no prefix (OrderStatus, BookingType)
114+
115+
## Code Quality Reminders
116+
- Use typed properties over docblocks
117+
- Prefer early returns over nested if/else
118+
- Use constructor property promotion when all properties can be promoted
119+
- Avoid else statements when possible
120+
- Use string interpolation over concatenation
121+
- Always use curly braces for control structures

src/Builders/ScheduleBuilder.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ public function from(Carbon|string $startDate): self
6060
return $this;
6161
}
6262

63+
/**
64+
* Alias of from()
65+
*/
66+
public function on(Carbon|string $startDate): self
67+
{
68+
return $this->from($startDate);
69+
}
70+
6371
/**
6472
* Set the end date.
6573
*/

src/Models/Concerns/HasSchedules.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ protected function scheduleBlocksTime(\Zap\Models\Schedule $schedule, string $da
175175

176176
// For non-recurring schedules: if no buffer, keep using the optimized overlapping scope
177177
if ($bufferMinutes <= 0) {
178-
return $schedule->periods()->overlapping($date, $startTime, $endTime)->exists();
178+
return $schedule->periods()->overlapping($date, $startTime, $endTime, $schedule->end_date ?? null)->exists();
179179
}
180180

181181
// With buffer, we need to evaluate in PHP

src/Models/SchedulePeriod.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Zap\Models;
44

55
use Carbon\Carbon;
6+
use Carbon\CarbonInterface;
67
use Illuminate\Database\Eloquent\Model;
78
use Illuminate\Database\Eloquent\Relations\BelongsTo;
89

@@ -139,9 +140,10 @@ public function scopeForTimeRange(\Illuminate\Database\Eloquent\Builder $query,
139140
/**
140141
* Scope a query to find overlapping periods.
141142
*/
142-
public function scopeOverlapping(\Illuminate\Database\Eloquent\Builder $query, string $date, string $startTime, string $endTime): \Illuminate\Database\Eloquent\Builder
143+
public function scopeOverlapping(\Illuminate\Database\Eloquent\Builder $query, string $date, string $startTime, string $endTime, ?CarbonInterface $endDate = null): \Illuminate\Database\Eloquent\Builder
143144
{
144-
return $query->whereDate('date', $date)
145+
return $query
146+
->when(is_null($endDate), fn ($query) => $query->whereDate('date', $date))
145147
->where('start_time', '<', $endTime)
146148
->where('end_time', '>', $startTime);
147149
}

src/Services/ValidationService.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function validate(
2525
}
2626

2727
// Period validation
28-
$periodErrors = $this->validatePeriods($periods);
28+
$periodErrors = $this->validatePeriods($periods, $rules);
2929
if (! empty($periodErrors)) {
3030
$errors = array_merge($errors, $periodErrors);
3131
}
@@ -91,7 +91,7 @@ protected function validateBasicAttributes(array $attributes): array
9191
/**
9292
* Validate schedule periods.
9393
*/
94-
protected function validatePeriods(array $periods): array
94+
protected function validatePeriods(array $periods, array $rules): array
9595
{
9696
$errors = [];
9797

@@ -107,7 +107,7 @@ protected function validatePeriods(array $periods): array
107107
}
108108

109109
foreach ($periods as $index => $period) {
110-
$periodErrors = $this->validateSinglePeriod($period, $index);
110+
$periodErrors = $this->validateSinglePeriod($period, $index, $rules);
111111
if (! empty($periodErrors)) {
112112
$errors = array_merge($errors, $periodErrors);
113113
}
@@ -127,7 +127,7 @@ protected function validatePeriods(array $periods): array
127127
/**
128128
* Validate a single period.
129129
*/
130-
protected function validateSinglePeriod(array $period, int $index): array
130+
protected function validateSinglePeriod(array $period, int $index, array $rules): array
131131
{
132132
$errors = [];
133133
$prefix = "periods.{$index}";
@@ -163,7 +163,7 @@ protected function validateSinglePeriod(array $period, int $index): array
163163
// Duration validation
164164
$duration = $start->diffInMinutes($end);
165165
$minDuration = config('zap.validation.min_period_duration', 15);
166-
$maxDuration = config('zap.validation.max_period_duration', 480);
166+
$maxDuration = data_get($rules, 'max_duration', config('zap.validation.max_period_duration'));
167167

168168
if ($duration < $minDuration) {
169169
$errors["{$prefix}.duration"] = "Period is too short ({$duration} minutes). Minimum duration is {$minDuration} minutes";
@@ -247,6 +247,7 @@ protected function validateRules(array $rules, Model $schedulable, array $attrib
247247
}
248248

249249
$ruleErrors = $this->validateRule($ruleName, $ruleConfig, $schedulable, $attributes, $periods);
250+
250251
if (! empty($ruleErrors)) {
251252
$errors = array_merge($errors, $ruleErrors);
252253
}

0 commit comments

Comments
 (0)