Laravel医院床位分配:可用床位校验与事件监听器实现问询
看起来你已经搭建好了基础的数据库结构,接下来我会一步步帮你实现床位的可用性检查、分配,以及用Laravel的事件监听器来串联整个流程。
一、先完善模型关联(必备基础)
首先,在Laravel中,模型关联能让我们更优雅地查询数据。你需要给三个模型添加关联关系:
Room 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Room extends Model { // 一个房间对应多个床位 public function beds() { return $this->hasMany(Bed::class); } }
Bed 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Bed extends Model { // 一个床位属于一个房间 public function room() { return $this->belongsTo(Room::class); } // 一个床位对应多个预订记录 public function reservations() { return $this->hasMany(Reservation::class); } }
Reservation 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Reservation extends Model { // 一个预订属于一个床位 public function bed() { return $this->belongsTo(Bed::class); } // 一个预订属于一个患者 public function patient() { return $this->belongsTo(Patient::class); } }
二、检查与获取可用床位
我们需要两个核心方法:一个用来批量获取某日期的可用床位,另一个用来单独验证某个床位在指定日期区间内是否可用。
1. 获取可用床位列表
在 Bed 模型中添加静态方法,用来查询指定日期的所有可用床位:
use Carbon\Carbon; public static function getAvailableBeds($date = null) { // 默认检查当天的可用情况,也可以传入指定日期 $targetDate = $date ?? Carbon::today(); return self::where('status', true) // 先筛选自身状态为可用的床位 ->whereDoesntHave('reservations', function ($query) use ($targetDate) { // 排除掉有有效且日期重叠的预订记录的床位 $query->where('status', true) ->where('date_in', '<=', $targetDate) ->where('date_out', '>=', $targetDate); }) ->with('room') // 关联查询房间信息,方便前端展示房间详情 ->get(); }
2. 验证单个床位的可用性
同样在 Bed 模型中添加方法,用来判断床位在指定日期区间内是否可用:
public function isAvailable($dateIn, $dateOut) { // 首先检查床位自身是否处于可用状态 if (!$this->status) { return false; } // 检查是否存在与指定日期区间重叠的有效预订 return !$this->reservations() ->where('status', true) ->where(function ($query) use ($dateIn, $dateOut) { // 覆盖三种日期重叠的情况 $query->whereBetween('date_in', [$dateIn, $dateOut]) ->orWhereBetween('date_out', [$dateIn, $dateOut]) ->orWhere(function ($q) use ($dateIn, $dateOut) { $q->where('date_in', '<=', $dateIn) ->where('date_out', '>=', $dateOut); }); }) ->exists(); }
3. 分配床位时的可用性校验
在你的 ReservationController 的 store 方法中,结合事务和上述方法确保分配的安全性:
use App\Models\Bed; use App\Models\Reservation; use Illuminate\Support\Facades\DB; use Illuminate\Http\Request; public function store(Request $request) { // 先验证输入数据 $validated = $request->validate([ 'patient_id' => 'required|exists:patients,id', 'bed_id' => 'required|exists:beds,id', 'date_in' => 'required|date', 'date_out' => 'required|date|after:date_in', ]); // 开启数据库事务,确保操作原子性 DB::beginTransaction(); try { $bed = Bed::findOrFail($validated['bed_id']); // 检查床位在指定日期内是否可用 if (!$bed->isAvailable($validated['date_in'], $validated['date_out'])) { return back()->withErrors(['bed_id' => '该床位在指定日期区间内已被占用']); } // 创建预订记录 $reservation = Reservation::create([ 'patient_id' => $validated['patient_id'], 'bed_id' => $validated['bed_id'], 'date_in' => $validated['date_in'], 'date_out' => $validated['date_out'], 'status' => true, ]); // 触发床位分配事件(后面会讲) event(new \App\Events\BedAssigned($reservation)); DB::commit(); return redirect()->route('reservations.show', $reservation)->with('success', '床位分配成功'); } catch (\Exception $e) { DB::rollBack(); return back()->withErrors(['message' => '分配失败:' . $e->getMessage()]); } }
三、用Laravel事件与监听器实现自动化流程
事件监听器能让你把床位分配后的后续操作(比如发送通知、更新床位状态)解耦,让代码更清晰。
1. 创建BedAssigned事件
运行Artisan命令生成事件:
php artisan make:event BedAssigned
然后修改 app/Events/BedAssigned.php,传入预订实例:
namespace App\Events; use App\Models\Reservation; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class BedAssigned { use Dispatchable, InteractsWithSockets, SerializesModels; public $reservation; public function __construct(Reservation $reservation) { $this->reservation = $reservation; } }
2. 创建监听器
我们可以创建多个监听器来处理不同的后续操作,比如发送通知、更新床位状态。
监听器1:发送患者通知
运行命令生成监听器:
php artisan make:listener SendBedAssignmentNotification --event=BedAssigned
修改 app/Listeners/SendBedAssignmentNotification.php:
namespace App\Listeners; use App\Events\BedAssigned; use Illuminate\Support\Facades\Mail; use App\Mail\BedAssignedMail; class SendBedAssignmentNotification { public function handle(BedAssigned $event) { $reservation = $event->reservation; $patient = $reservation->patient; // 发送邮件通知患者 Mail::to($patient->email)->send(new BedAssignedMail($reservation)); // 同时记录操作日志 \Log::info("床位分配日志:患者 {$patient->name} 成功分配到 {$reservation->bed->room->name} 房间的 {$reservation->bed->name} 床位"); } }
监听器2:更新床位状态(可选)
如果你需要把床位状态改为“已占用”,可以再创建一个监听器:
php artisan make:listener UpdateBedStatusListener --event=BedAssigned
修改 app/Listeners/UpdateBedStatusListener.php:
namespace App\Listeners; use App\Events\BedAssigned; class UpdateBedStatusListener { public function handle(BedAssigned $event) { $bed = $event->reservation->bed; // 将床位状态改为不可用 $bed->update(['status' => false]); } }
3. 创建邮件模板
运行命令生成邮件类:
php artisan make:mail BedAssignedMail
修改 app/Mail/BedAssignedMail.php:
namespace App\Mail; use App\Models\Reservation; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; class BedAssignedMail extends Mailable { use Queueable, SerializesModels; public $reservation; public function __construct(Reservation $reservation) { $this->reservation = $reservation; } public function envelope() { return new Envelope(subject: '床位分配成功通知'); } public function content() { return new Content(view: 'emails.bed-assigned'); } public function attachments() { return []; } }
然后创建邮件视图 resources/views/emails/bed-assigned.blade.php:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>床位分配成功</title> </head> <body style="font-family: Arial, sans-serif; line-height: 1.6;"> <h2>尊敬的 {{ $reservation->patient->name }}:</h2> <p>您好!您的床位已成功分配,详情如下:</p> <ul> <li>房间:{{ $reservation->bed->room->name }}({{ $reservation->bed->room->floor }}楼)</li> <li>床位编号:{{ $reservation->bed->name }}</li> <li>入住日期:{{ $reservation->date_in->format('Y年m月d日') }}</li> <li>退房日期:{{ $reservation->date_out->format('Y年m月d日') }}</li> </ul> <p>请您按时到医院办理入住手续,如有疑问请联系医护人员。</p> <p>祝您早日康复!</p> </body> </html>
4. 注册事件与监听器
打开 app/Providers/EventServiceProvider.php,在 $listen 数组中注册事件和对应的监听器:
protected $listen = [ \App\Events\BedAssigned::class => [ \App\Listeners\SendBedAssignmentNotification::class, \App\Listeners\UpdateBedStatusListener::class, ], ];
这样,当你在控制器中触发 event(new BedAssigned($reservation)) 时,两个监听器会自动执行对应的操作。
内容的提问来源于stack exchange,提问作者Asmaa Muhammed

