简体   繁体   English

Laravel 是开始和结束之间的时间(防止重复预订)

[英]Laravel is a time between a start & end (Prevent double booking)

I'm writing tests to make sure that a new reservation can not be double-booked over another another.我正在编写测试以确保一个新的预订不能重复预订另一个。 I've read though countless other SO threads and now I'm more confused and not sure I'm doing anything right.我已经阅读了无数其他 SO 线程,现在我更加困惑,不确定我做对了什么。 I'm specifically using Laravel in my project as well as this example.我在我的项目和这个例子中专门使用了 Laravel。

Migration.php迁移.php

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

I've worked with dateTime or timezone .我使用过dateTimetimezone I get into a trap of, "I don't need the date, just the time. I'm saving date somewhere else."我陷入了一个陷阱,“我不需要日期,只需要时间。我正在其他地方保存日期。” I'll then find a thread that suggests saving as timestamp and compare the date there as well.然后我会找到一个建议保存为timestamp的线程并比较那里的日期。

I have a reservation factory to generate (among other details) date , time_start and time_end :我有一个预订工厂来生成(以及其他细节) datetime_starttime_end

Factory.php工厂.php

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

Most threads I've read suggest comparing using strtotime .我读过的大多数线程都建议使用strtotime进行比较。 Something like:就像是:

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

This makes sense.这是有道理的。 But then I read that saving as dateTime or timezone is better because of timezone.但是后来我读到保存为dateTimetimezone更好,因为时区。

In my controller I am checking for an existing reservation like this:在我的控制器中,我正在检查这样的现有预订:

Controller.php控制器.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
}
...

Using whereTime looks like exactly what I need:使用whereTime看起来正是我需要的:

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

It looks like that would be saved as a time column.看起来这将被保存为time列。 In my tests I am checking that I get a 400 back if it can't be created.在我的测试中,我正在检查如果无法创建它是否会返回400

Test.php测试文件

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

This works great.这很好用。 If I create a reservation that is 15:00:00 to 17:00:00 same date/asset etc. My test passes.如果我创建了一个15:00:0017:00:00相同日期/资产等的预订。我的测试通过了。 I get the 400 error back exactly as I expect.我完全按照我的预期得到了 400 错误。 However, if I pass 15:01:00 My test fails.但是,如果我通过15:01:00我的测试失败。 Not surprised, but that tells me I am not handling the comparison correctly.并不感到惊讶,但这告诉我我没有正确处理比较。 It seems like I am right at the finish line, but then both shoes have come untied.看起来我就在终点线,但后来两只鞋都解开了。

The UI will just be a drop down with human-readable times. UI 将只是一个带有人类可读时间的下拉菜单。 I was planning on just saving the values as 24 hr time.我打算只将值保存为 24 小时时间。 For example, 15:00:00 .例如, 15:00:00 I'm not sure how else to do that...我不知道该怎么做...

I would be grateful for suggestions to better understand how:我将不胜感激建议以更好地了解如何:

  • Best save the time(s) time , timestamp , datetime ?最好保存时间timetimestampdatetime time
  • Use strtotime (or not).使用strtotime (或不使用)。 If so, what's the ideal data type?如果是这样,理想的数据类型是什么? timestamp , datetime ? timestampdatetime timestamp
  • Use the appropriate error response;使用适当的错误响应; is 400 ideal? 400理想吗?

Thank you so much for any thoughts.非常感谢您的任何想法。

UPDATE更新

Following @miken32 suggestion - I did have the relationships already set up that way so it made sense.按照@miken32 的建议 - 我确实已经建立了这种关系,所以这是有道理的。

I am now saving time_start and time_end as a dateTime field in my migration.我现在将time_starttime_end保存为迁移中的dateTime字段。

Controller.php控制器.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);

Users will only have the option to choose pre-determined time(s).用户只能选择预先确定的时间。 Once a time slot has been reserved for any given asset, that block is un-available.一旦为任何给定资产预留了一个时间段,该区块将不可用。 I feel confident that I'm in essence making sure that someone can't (somehow) override the POST request with a different value.我确信我本质上是在确保某人不能(以某种方式)用不同的值覆盖POST请求。

Hopefully this will help someone else.希望这会帮助别人。 If my implementation is off, please let me know so I can correct it for everyone else.如果我的实施被关闭,请告诉我,以便我可以为其他人纠正它。

You would be better off storing this information as two DATETIME columns.最好将此信息存储为两个DATETIME列。 Advantages include being able to take advantage of Laravel's built-in casting to Carbon dates, and avoiding trouble with booking appointments over midnight.优点包括能够利用 Laravel 内置的 Carbon 日期转换,并避免在午夜预约约会的麻烦。

Then, assuming $request->time_start and $request->time_end are full date/times, your query becomes something like this:然后,假设$request->time_start$request->time_end是完整的日期/时间,您的查询将变成这样:

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

You'd also add time_start and time_end to your model's $dates array to take advantage of automatic casting .您还可以将time_starttime_end添加到模型的$dates数组中以利用自动转换

And speaking of models, if your relationships are set up correctly, this query could be like this, instead of using the DB facade:说到模型,如果你的关系设置正确,这个查询可能是这样的,而不是使用DB门面:

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

It's no shorter, but IMO it makes it easier to see at a glance what's being searched for.它并不短,但 IMO 可以更容易地一目了然地看到正在搜索的内容。


As for HTTP responses, it doesn't really matter what you use.至于 HTTP 响应,您使用什么并不重要。 It's all your code, so you know what to expect.这是你的全部代码,所以你知道会发生什么。 But if you want to be pedantic (which I fully support) perhaps 409 might suit your needs?但是,如果您想学究(我完全支持),也许409可能适合您的需求?

The 409 (Conflict) status code indicates that the request could not be completed due to a conflict with the current state of the target resource. 409(冲突)状态码表示由于与目标资源的当前状态冲突,请求无法完成。 This code is used in situations where the user might be able to resolve the conflict and resubmit the request.此代码用于用户可能能够解决冲突并重新提交请求的情况。 The server SHOULD generate a payload that includes enough information for a user to recognize the source of the conflict.服务器应该生成一个负载,其中包含足够的信息让用户识别冲突的来源。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM