'datetime', 'confirmed_at' => 'datetime', 'blocked_at' => 'datetime', 'resolved_at' => 'datetime', ]; } public function shopping_user(): BelongsTo { return $this->belongsTo(ShoppingUser::class, 'shopping_user_id'); } public function requested_by(): BelongsTo { return $this->belongsTo(User::class, 'requested_by_user_id'); } public static function hashToken(string $token): string { return hash('sha256', $token); } /** * Open requests that can still be confirmed. * * @param Builder $query */ public function scopePending(Builder $query): void { $query->whereNull('confirmed_at')->whereNotNull('token_hash')->where('expires_at', '>', now()); } /** * Changes rejected because of customer sovereignty that an admin has not handled yet. * * @param Builder $query */ public function scopeBlockedOpen(Builder $query): void { $query->whereNotNull('blocked_at')->whereNull('resolved_at'); } /** * Requests belonging to the same customer (all entries share the customer number). * * @param Builder $query */ public function scopeForCustomer(Builder $query, ShoppingUser $shoppingUser): void { if ($shoppingUser->number > 0) { $query->where('number', $shoppingUser->number); } else { $query->where('shopping_user_id', $shoppingUser->id); } } public function isConfirmable(): bool { return $this->confirmed_at === null && $this->token_hash !== null && $this->expires_at?->isFuture(); } }