initial commit

This commit is contained in:
bastien
2026-06-28 12:29:53 +02:00
parent 03e85d2f9d
commit b2443b654c
220 changed files with 24969 additions and 1 deletions
@@ -0,0 +1,219 @@
<?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',
]);
}
}