1. API docs via dedoc/scramble at /docs/api (scoped to api/v1).
Linked from the admin sidebar Settings group.
2. Scheduled commands registered in routes/console.php:
- reports:aggregate (02:00) — daily/weekly/monthly aggregations
- qr:expire (02:30) — flips past-due allocated/active codes to expired
- trucks:prune-locations (03:00) — drops history older than retention
window (default 7 days, config('verde.location_retention_days'))
All idempotent + withoutOverlapping. --dry flags on qr:expire and
trucks:prune-locations for safe inspection.
3. Trip double-booking validation: AdminTripController::store rejects
new trips when the team or truck already has a non-cancelled trip on
the same date. override_conflicts: true bypasses for emergencies.
Cancelled trips don't block rebooking.
4a. Email verification: User implements MustVerifyEmail.
VerifyEmailNotification overrides verificationUrl() for our
namespaced route. Register sends the link automatically (best
effort, won't block signup). POST /auth/email/resend (auth) +
GET /auth/email/verify/{id}/{hash} (signed URL).
4b. Password change while logged in: POST /me/password validates
current_password, requires the new password to differ, revokes
every other active token on success — current session stays.
5a. PickupImminent notification: when TripStop -> arrived,
TripExecutor::notifyAssignedHouseholds() finds households whose
assigned_drop_off_point_id matches and sends DB + SMS.
5b. Auto-geofence on truck location: TruckTracker::record() now
auto-fires TripExecutor::arriveAtDumpsite() when an in-progress
trip's truck pings inside its dumpsite boundary. The executor's
status guard prevents duplicate timeline events if the driver also
presses arrive-dumpsite manually.
190 feature tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 lines
1.3 KiB
PHP
48 lines
1.3 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\QrCode;
|
|
use App\States\QrCode\Active;
|
|
use App\States\QrCode\Allocated;
|
|
use App\States\QrCode\Expired;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
class ExpireQrCodes extends Command
|
|
{
|
|
protected $signature = 'qr:expire {--dry : Report what would change without writing}';
|
|
|
|
protected $description = 'Transition QR codes whose expires_at has passed to the expired state';
|
|
|
|
public function handle(): int
|
|
{
|
|
$now = Carbon::now();
|
|
$dry = (bool) $this->option('dry');
|
|
|
|
$query = QrCode::query()
|
|
->whereNotNull('expires_at')
|
|
->where('expires_at', '<=', $now)
|
|
->whereIn('status', [Allocated::$name, Active::$name]);
|
|
|
|
$count = (clone $query)->count();
|
|
$this->info(($dry ? '[DRY] ' : '')."Found {$count} expired QR codes");
|
|
|
|
if ($count === 0 || $dry) {
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
// Bulk update — bypasses the state machine but keeps the DB
|
|
// representation consistent and is safe because we only move
|
|
// from non-terminal states to a terminal one.
|
|
$updated = $query->update([
|
|
'status' => Expired::$name,
|
|
'updated_at' => $now,
|
|
]);
|
|
|
|
$this->info("Expired {$updated} QR codes");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
}
|