Laravel - это время между началом и концом (предотвращение двойного бронирования) - PullRequest
2 голосов
/ 20 февраля 2020

Я пишу тесты, чтобы удостовериться, что новое бронирование не может быть зарезервировано дважды над другим. Я прочитал, хотя и бесчисленное количество других SO-тем, и теперь я в замешательстве и не уверен, что все делаю правильно. Я специально использую Laravel в своем проекте, а также в этом примере.

Миграция. php

...
$table->date('date');       // 2020-01-01
$table->time('time_start'); // 15:00:00
$table->time('time_end');   // 17:00:00
...

Я работал с dateTime или timezone. Я попадаю в ловушку: «Мне не нужна дата , просто время. Я сохраняю дату в другом месте». Затем я найду ветку, предлагающую сохранить как timestamp, и сравню там дату.

У меня есть фабрика резервирования для генерации (среди прочих деталей) date, time_start и time_end:

Factory. php

'date' => date('Y-m-d'),
'time_start' => '15:00:00',
'time_end' => '17:00:00',

Большинство прочитанных мной тем предлагают сравнение с использованием strtotime. Что-то вроде:

'time_start' => strtotime('15:00:00'),  // 1582210800

Это имеет смысл. Но потом я прочитал, что сохранение как dateTime или timezone лучше из-за часового пояса.

В моем контроллере я проверяю существующее резервирование, как это:

Controller. php

...
$existing = DB::table('reservations')
    ->where('asset_id', '=', $request->asset_id)
    ->whereDate('date', '=', $request->date)
    ->whereTime('time_start', '>=', $request->time_start)  // or use $request->strtotime('time_start')
    ->whereTime('time_end', '<=', $request->time_end)
    ->where(function ($query) {
        $query
            ->where('status', '=', 'created')
            ->orWhere('status', '=', 'pending')
            ->orWhere('status', '=', 'completed');
    })
    ->get();

if ($existing->count() > 0) {
    // Not allowed
} else {
    // OK to proceed
}
...

Использование whereTime выглядит именно так, как мне нужно:

->whereTime('created_at', '=', '11:20:45') 

Похоже, что это будет сохранено как time столбец. В своих тестах я проверяю, что я получаю 400 обратно, если он не может быть создан.

Тест. php

...
$http->assertStatus(400)
    ->assertJsonStructure([
        'type', 'data' => [
            'reason'
        ]])
        ->assertJson([
            'type' => 'reservations',
            'data' => [
                'reason' => 'Asset is no longer available.',
            ],
        ]);

Это работает Великий. Если я создаю бронирование с 15:00:00 до 17:00:00 той же даты / актив и т. Д. 1107 *. Мой тест проходит. Я получаю ошибку 400 точно так, как я ожидаю. Однако, если я пройду 15:01:00, мой тест не пройден. Не удивлен, но это говорит о том, что я не правильно сравниваю данные. Кажется, что я нахожусь на финишной линии sh, но тогда обе туфли развязались.

Интерфейс пользователя будет просто выпадать из понятных человеку времен. Я планировал просто сохранить значения как 24-часовое время. Например, 15:00:00. Я не уверен, как еще это сделать ...

Буду признателен за предложения, чтобы лучше понять, как:

  • Лучше всего сэкономить время (я) time, timestamp, datetime?
  • Использовать strtotime (или нет). Если да, то какой тип данных идеален? timestamp, datetime?
  • Используйте соответствующий ответ об ошибке; 400 идеально?

Большое спасибо за любые мысли.

ОБНОВЛЕНИЕ

Следуя совету @ miken32 - я сделал уже установили отношения таким образом, чтобы это имело смысл.

Теперь я сохраняю time_start и time_end как поле dateTime в моей миграции.

Контроллер. php

$asset = Asset::find($request->asset_id);

$existing = $asset->reservations()
    ->where(function ($query) use ($request) {
        $start_dt = new Carbon($request->time_start);
        $end_dt = new Carbon($request->time_end);

        $query->where('time_start', '>=', $start_dt)
            ->where('time_end', '<=', $end_dt);
        })
        ->whereIn('status', ['created', 'pending', 'completed'])
        ->get();

    if ($existing->count() > 0) {
        // Log::info('CANNOT MAKE RESERVATION FOR: ' . $request->first_name . ' ' . $request->last_name);
        return response()->json(['type' => 'reservations', 'data' => ['reason' => 'Asset is no longer available.']], 409);
    } else {
        $reservation = new Reservation();
        ...
        // Log::info('RESERVATION MADE FOR: ' . $reservation->first_name . ' ' . $reservation->last_name);

Пользователи будут иметь возможность выбирать только предварительно определенное время. Как только временной интервал зарезервирован для любого данного актива, этот блок становится недоступным. Я уверен, что, по сути, я уверен, что кто-то не может (каким-то образом) переопределить запрос POST другим значением.

Надеюсь, это поможет кому-то еще. Если моя реализация выключена, пожалуйста, дайте мне знать, чтобы я мог исправить это для всех остальных.

1 Ответ

2 голосов
/ 21 февраля 2020

Вам лучше хранить эту информацию в виде двух DATETIME столбцов. Преимущества включают возможность использования встроенного в Laravel приведения к датам Carbon и избежание проблем при бронировании встреч в течение полуночи.

Затем, предполагая, что $request->time_start и $request->time_end являются полной датой / раз ваш запрос становится примерно таким:

$existing = DB::table('reservations')
    ->where('asset_id', $request->asset_id)
    ->where(function($q) {
        $q->whereBetween('time_start', [$request->time_start, $request->time_end])
            ->orWhereBetween('time_end', [$request->time_start, $request->time_end])
            ->orWhere(function($q) use ($request) {
                $q->where('time_start', '<', $request->time_start)
                    ->where('time_end', '>', $request->time_end);
            });
    })
    ->whereIn('status', ['created', 'pending', 'completed'])
    ->get();

Вы также добавили бы time_start и time_end к массиву $dates вашей модели, чтобы воспользоваться преимуществами automati c casting .

И если говорить о моделях, если ваши отношения установлены правильно, этот запрос может быть таким, вместо использования DB фасад:

$asset = Asset::find($request->asset_id);
$existing = $asset
    ->reservations()
    ->where(function($q) use($request) {
        $q->whereBetween('time_start', [$request->time_start, $request->time_end])
            ->orWhereBetween('time_end', [$request->time_start, $request->time_end])
            ->orWhere(function($q) use ($request) {
                $q->where('time_start', '<', $request->time_start)
                    ->where('time_end', '>', $request->time_end);
            });
    })
    ->whereIn('status', ['created', 'pending', 'completed'])
    ->get();

Это не короче , но IMO облегчает сразу увидеть, что ищут.


Что касается HTTP-ответов, то не имеет значения, что вы используете. Это весь ваш код, так что вы знаете, чего ожидать. Но если вы хотите быть pedanti c (что я полностью поддерживаю), возможно, 409 может удовлетворить ваши потребности?

Код состояния 409 (Конфликт) указывает, что запрос может не будет завершено из-за конфликта с текущим состоянием целевого ресурса. Этот код используется в ситуациях, когда пользователь может разрешить конфликт и повторно отправить запрос. Сервер ДОЛЖЕН генерировать полезную нагрузку, которая включает в себя достаточно информации, чтобы пользователь мог распознать источник конфликта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...