220 lines
7.5 KiB
PHP
220 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace App\Domain\Integration;
|
|
|
|
use App\Jobs\SimulateCycleCompletion;
|
|
use App\Models\Machine;
|
|
use App\Models\MachineCommand;
|
|
use App\Models\MachineEvent;
|
|
use App\Models\MachineStatusHistory;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Str;
|
|
|
|
class SimulatedMachineProvider implements MachineProviderInterface
|
|
{
|
|
public function startCycle(Machine $machine, array $payload): MachineCommandResult
|
|
{
|
|
$correlationId = (string) Str::uuid();
|
|
$externalReference = 'sim-'.Str::random(12);
|
|
$provider = config('laverie.simulation.provider_name', 'simulated');
|
|
$durationSeconds = (int) config('laverie.simulation.cycle_duration_seconds', 30);
|
|
$cycleEndsAt = now()->addSeconds($durationSeconds);
|
|
|
|
$command = MachineCommand::query()->create([
|
|
'uuid' => (string) Str::uuid(),
|
|
'machine_id' => $machine->id,
|
|
'provider' => $provider,
|
|
'command_type' => 'start_cycle',
|
|
'payload' => $payload,
|
|
'status' => 'acknowledged',
|
|
'external_reference' => $externalReference,
|
|
'correlation_id' => $correlationId,
|
|
'requested_by_user_id' => $payload['requested_by_user_id'] ?? null,
|
|
'sent_at' => now(),
|
|
'responded_at' => now(),
|
|
]);
|
|
|
|
$previousStatus = $machine->status;
|
|
|
|
$machine->update([
|
|
'status' => 'running',
|
|
'current_user_id' => $payload['requested_by_user_id'] ?? $machine->current_user_id,
|
|
'cycle_started_at' => now(),
|
|
'cycle_ends_at' => $cycleEndsAt,
|
|
'last_heartbeat_at' => now(),
|
|
]);
|
|
|
|
$this->recordStatusChange($machine, $previousStatus, 'running', $provider, 'cycle_started');
|
|
$this->recordEvent($machine, $provider, 'cycle_started', [
|
|
'command_uuid' => $command->uuid,
|
|
'wash_uuid' => $payload['wash_uuid'] ?? null,
|
|
]);
|
|
|
|
SimulateCycleCompletion::dispatch(
|
|
$machine->id,
|
|
$command->id,
|
|
$payload['wash_id'] ?? null,
|
|
)->delay($cycleEndsAt);
|
|
|
|
return MachineCommandResult::success('acknowledged', $externalReference, $correlationId);
|
|
}
|
|
|
|
public function stopCycle(Machine $machine, array $payload = []): MachineCommandResult
|
|
{
|
|
$provider = config('laverie.simulation.provider_name', 'simulated');
|
|
$correlationId = (string) Str::uuid();
|
|
|
|
MachineCommand::query()->create([
|
|
'uuid' => (string) Str::uuid(),
|
|
'machine_id' => $machine->id,
|
|
'provider' => $provider,
|
|
'command_type' => 'stop_cycle',
|
|
'payload' => $payload,
|
|
'status' => 'acknowledged',
|
|
'correlation_id' => $correlationId,
|
|
'sent_at' => now(),
|
|
'responded_at' => now(),
|
|
]);
|
|
|
|
$previousStatus = $machine->status;
|
|
|
|
$machine->update([
|
|
'status' => 'available',
|
|
'current_user_id' => null,
|
|
'cycle_started_at' => null,
|
|
'cycle_ends_at' => null,
|
|
'last_heartbeat_at' => now(),
|
|
]);
|
|
|
|
$this->recordStatusChange($machine, $previousStatus, 'available', $provider, 'cycle_stopped');
|
|
$this->recordEvent($machine, $provider, 'cycle_completed', ['reason' => 'manual_stop']);
|
|
|
|
return MachineCommandResult::success('acknowledged', null, $correlationId);
|
|
}
|
|
|
|
public function refreshStatus(Machine $machine): MachineStatusSnapshot
|
|
{
|
|
$machine->refresh();
|
|
|
|
return new MachineStatusSnapshot(
|
|
status: $machine->status,
|
|
lastHeartbeat: $machine->last_heartbeat_at,
|
|
metadata: [
|
|
'cycle_started_at' => $machine->cycle_started_at?->toIso8601String(),
|
|
'cycle_ends_at' => $machine->cycle_ends_at?->toIso8601String(),
|
|
'current_user_id' => $machine->current_user_id,
|
|
],
|
|
);
|
|
}
|
|
|
|
public function handleInboundEvent(array $payload): void
|
|
{
|
|
$externalMachineId = $payload['external_machine_id'] ?? $payload['machine_id'] ?? null;
|
|
|
|
if ($externalMachineId === null) {
|
|
return;
|
|
}
|
|
|
|
$integration = \App\Models\MachineIntegration::query()
|
|
->where('external_machine_id', (string) $externalMachineId)
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
if ($integration === null) {
|
|
return;
|
|
}
|
|
|
|
$machine = $integration->machine;
|
|
$provider = $integration->provider;
|
|
$eventType = $payload['event_type'] ?? 'status_changed';
|
|
|
|
if ($eventType === 'heartbeat') {
|
|
$machine->update(['last_heartbeat_at' => now()]);
|
|
|
|
if ($machine->status === 'offline') {
|
|
$previousStatus = $machine->status;
|
|
$machine->update(['status' => 'available']);
|
|
$this->recordStatusChange($machine, $previousStatus, 'available', $provider, 'heartbeat');
|
|
}
|
|
}
|
|
|
|
if ($eventType === 'machine_offline') {
|
|
$previousStatus = $machine->status;
|
|
$machine->update(['status' => 'offline']);
|
|
$this->recordStatusChange($machine, $previousStatus, 'offline', $provider, 'machine_offline');
|
|
}
|
|
|
|
if ($eventType === 'machine_online') {
|
|
$previousStatus = $machine->status;
|
|
$machine->update(['status' => 'available', 'last_heartbeat_at' => now()]);
|
|
$this->recordStatusChange($machine, $previousStatus, 'available', $provider, 'machine_online');
|
|
}
|
|
|
|
if ($eventType === 'cycle_completed') {
|
|
$previousStatus = $machine->status;
|
|
$machine->update([
|
|
'status' => 'available',
|
|
'current_user_id' => null,
|
|
'cycle_started_at' => null,
|
|
'cycle_ends_at' => null,
|
|
'last_heartbeat_at' => now(),
|
|
]);
|
|
$this->recordStatusChange($machine, $previousStatus, 'available', $provider, 'cycle_completed');
|
|
}
|
|
|
|
if ($eventType === 'error_reported') {
|
|
$previousStatus = $machine->status;
|
|
$machine->update(['status' => 'error']);
|
|
$this->recordStatusChange($machine, $previousStatus, 'error', $provider, 'error_reported');
|
|
}
|
|
|
|
$this->recordEvent(
|
|
$machine,
|
|
$provider,
|
|
$eventType,
|
|
$payload,
|
|
$payload['external_event_id'] ?? null,
|
|
);
|
|
}
|
|
|
|
private function recordStatusChange(
|
|
Machine $machine,
|
|
?string $previousStatus,
|
|
string $newStatus,
|
|
string $source,
|
|
string $reason,
|
|
): void {
|
|
MachineStatusHistory::query()->create([
|
|
'machine_id' => $machine->id,
|
|
'previous_status' => $previousStatus,
|
|
'new_status' => $newStatus,
|
|
'source' => $source,
|
|
'reason' => $reason,
|
|
'created_at' => Carbon::now(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
private function recordEvent(
|
|
Machine $machine,
|
|
string $provider,
|
|
string $eventType,
|
|
array $payload,
|
|
?string $externalEventId = null,
|
|
): void {
|
|
MachineEvent::query()->create([
|
|
'machine_id' => $machine->id,
|
|
'provider' => $provider,
|
|
'external_event_id' => $externalEventId,
|
|
'event_type' => $eventType,
|
|
'payload' => $payload,
|
|
'occurred_at' => now(),
|
|
'received_at' => now(),
|
|
'processed_at' => now(),
|
|
'processing_status' => 'processed',
|
|
]);
|
|
}
|
|
}
|