I want a user to save his presence at a location. The presence can be all day or from e.g. 10am to 8pm. There is the following form:
Location: [
'Location A',
'Location B',
'Location C'
]
Start Date: [Y-m-d]
Start Time: [H:i] (only shown if allDay = false)
End Date: [Y-m-d]
End Time: [H:i] (only shown if allDay = false)
allDay: [x]
Frequency: [
'once',
'weekly',
'every 2nd week',
'monthly'
]
User id: [hidden]
I thought about having this table:
CREATE TABLE `presences` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned DEFAULT NULL,
`location_id` bigint unsigned DEFAULT NULL,
`start` datetime NOT NULL,
`end` datetime NOT NULL,
`frequency` varchar(255) NOT NULL,
`interval` int NOT NULL,
`allDay` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `presences_location_id_foreign` (`location_id`),
KEY `presences_user_id_foreign` (`user_id`),
CONSTRAINT `presences_location_id_foreign` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`) ON DELETE CASCADE,
CONSTRAINT `presences_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
For the frequency I could store one of ['once','weekly','monthly'] where interval is always one, expect when weekly2 is selected, it is 2.
The issue that I have is:
E.g. presence is planned from 2022-08-19 08:00 to 2022-08-21 18:00 for a calendar this would be one 58h event.
But from the users perspective this means every day between 19th and 21th from 08am to 6pm each.
And this is what I finally want to display in a calendar: For the given example I want to show 3 events (19th, 20th, 21th) starting from 8am to 6pm each, but now I need to add extra logic the data in the database and I can't just query
Give me the presence of the user at the location
I already created that logic in php:
public function spreadBetween($presence, $start, $end)
{
$presences = [];
$period = CarbonPeriod::create($start->format('Y-m-d'), $end->format('Y-m-d'));
$periodPresence = CarbonPeriod::create($presence->start->format('Y-m-d'), $presence->end->format('Y-m-d'));
foreach ($period as $date) {
if ($periodPresence->contains($date)) {
$presence->start = Carbon::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d') . ' ' . $presence->start->format('H:i:s'));
$presence->end = Carbon::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d') . ' ' . $presence->end->format('H:i:s'));
$presences[] = $presence->replicate();
}
}
return $presences;
}
And now comes the very tricky part that lets me believe I'm not doing it right:
If you plan a meeting with a user at a location at a specific time, the user is present, but not available. This block should not be part of the final presence result.
This is why I thought it makes sense, to not split the days in to events. I need to split them further in hours:
public function createPresenceSlots(int $uid, int $lid, string $start, string $end, $duration)
{
$group = uniqid();
$start = Carbon::parse($start);
$end = Carbon::parse($end);
$presences = [];
$period = CarbonPeriod::create($start, $end);
foreach ($period as $date) {
$intervalEnd = Carbon::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d') . ' ' . $end->format('H:i:s'));
while ($date <= $intervalEnd) {
$presences[] = Presence::create([
'user_id' => $uid,
'location_id' => $lid,
'end' => $end,
'allDay' => 0,
]);
$date->addMinutes($duration);
}
}
return $presences;
}
}
Then I could just delete a row if the user is taken. But I would end up with a million data rows with that approach.
And now here I am, asking how to do this correctly and with a more intuitive queryable way.