netgescon-day0/app/Http/Controllers/Api/WaterMeterGatewayController.php

174 lines
7.4 KiB
PHP
Executable File

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\StabileServizio;
use App\Models\StabileServizioLettura;
use App\Models\UnitaImmobiliare;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
class WaterMeterGatewayController extends Controller
{
public function storeReading(Request $request): JsonResponse
{
$expectedToken = trim((string) config('services.water_gateway.token', ''));
if ($expectedToken !== '') {
$provided = trim((string) ($request->header('X-Netgescon-Water-Token') ?: $request->bearerToken() ?: $request->query('token', '')));
if (! hash_equals($expectedToken, $provided)) {
return response()->json(['ok' => false, 'message' => 'Token gateway non valido'], 403);
}
} elseif (! app()->environment(['local', 'testing'])) {
return response()->json(['ok' => false, 'message' => 'Token gateway acqua non configurato'], 503);
}
$data = $request->validate([
'device_id' => ['nullable', 'string', 'max:120'],
'serial' => ['nullable', 'string', 'max:100'],
'unita_id' => ['nullable', 'integer'],
'value' => ['required', 'numeric'],
'read_at' => ['nullable', 'date'],
'unit' => ['nullable', 'string', 'max:20'],
'battery' => ['nullable', 'numeric'],
'rssi' => ['nullable', 'numeric'],
'payload' => ['nullable', 'array'],
]);
$unita = $this->resolveUnita($data);
if (! $unita instanceof UnitaImmobiliare) {
return response()->json(['ok' => false, 'message' => 'Unità non trovata per device_id/seriale'], 404);
}
$servizio = StabileServizio::query()
->where('stabile_id', (int) $unita->stabile_id)
->where('tipo', 'acqua')
->where('attivo', true)
->orderBy('id')
->first();
if (! $servizio instanceof StabileServizio) {
$servizio = StabileServizio::query()->create([
'stabile_id' => (int) $unita->stabile_id,
'tipo' => 'acqua',
'nome' => 'Acqua - gateway contatori',
'contatore_matricola' => trim((string) ($data['serial'] ?? '')) ?: null,
'attivo' => true,
'meta' => ['source' => 'water_gateway_auto'],
]);
}
$readAt = isset($data['read_at']) && $data['read_at'] ? Carbon::parse($data['read_at']) : now();
$value = round((float) $data['value'], 3);
$previous = StabileServizioLettura::query()
->where('unita_immobiliare_id', (int) $unita->id)
->where('stabile_servizio_id', (int) $servizio->id)
->whereNotNull('lettura_fine')
->orderByDesc('periodo_al')
->orderByDesc('id')
->first();
$previousValue = $previous instanceof StabileServizioLettura ? (float) $previous->lettura_fine : null;
$consumption = $previousValue !== null ? max(0, round($value - $previousValue, 3)) : 0.0;
$reading = StabileServizioLettura::query()->create([
'stabile_id' => (int) $unita->stabile_id,
'stabile_servizio_id' => (int) $servizio->id,
'unita_immobiliare_id' => (int) $unita->id,
'periodo_dal' => $previous?->periodo_al ?: $readAt->toDateString(),
'periodo_al' => $readAt->toDateString(),
'tipologia_lettura' => 'reale',
'canale_acquisizione' => 'gateway',
'riferimento_acquisizione' => trim((string) ($data['device_id'] ?? $data['serial'] ?? 'gateway')),
'workflow_stato' => 'acquisita',
'rilevatore_tipo' => 'gateway_iot',
'rilevatore_nome' => trim((string) ($data['device_id'] ?? 'gateway')),
'lettura_precedente_valore' => $previousValue,
'lettura_inizio' => $previousValue,
'lettura_fine' => $value,
'consumo_valore' => $consumption,
'consumo_unita' => trim((string) ($data['unit'] ?? 'mc')) ?: 'mc',
'raw' => [
'source' => 'water_meter_gateway',
'device_id' => $data['device_id'] ?? null,
'serial' => $data['serial'] ?? null,
'battery' => $data['battery'] ?? null,
'rssi' => $data['rssi'] ?? null,
'payload' => $data['payload'] ?? [],
],
]);
$this->updateUnitGatewayFields($unita, $data);
return response()->json([
'ok' => true,
'reading_id' => (int) $reading->id,
'unita_id' => (int) $unita->id,
'stabile_id' => (int) $unita->stabile_id,
'stabile_servizio_id' => (int) $servizio->id,
'lettura_fine' => $value,
'consumo' => $consumption,
]);
}
/** @param array<string,mixed> $data */
private function resolveUnita(array $data): ?UnitaImmobiliare
{
$query = UnitaImmobiliare::query();
if (Schema::hasColumn('unita_immobiliari', 'deleted_at')) {
$query->whereNull('deleted_at');
}
if (! empty($data['unita_id'])) {
$found = (clone $query)->whereKey((int) $data['unita_id'])->first();
if ($found instanceof UnitaImmobiliare) {
return $found;
}
}
$deviceId = trim((string) ($data['device_id'] ?? ''));
if ($deviceId !== '' && Schema::hasColumn('unita_immobiliari', 'acqua_gateway_device_id')) {
$found = (clone $query)->where('acqua_gateway_device_id', $deviceId)->first();
if ($found instanceof UnitaImmobiliare) {
return $found;
}
}
$serial = trim((string) ($data['serial'] ?? ''));
if ($serial !== '' && Schema::hasColumn('unita_immobiliari', 'acqua_contatore_seriale')) {
$found = (clone $query)->where('acqua_contatore_seriale', $serial)->first();
if ($found instanceof UnitaImmobiliare) {
return $found;
}
}
return null;
}
/** @param array<string,mixed> $data */
private function updateUnitGatewayFields(UnitaImmobiliare $unita, array $data): void
{
$patch = [];
if (empty($unita->acqua_gateway_device_id) && ! empty($data['device_id'])) {
$patch['acqua_gateway_device_id'] = trim((string) $data['device_id']);
}
if (empty($unita->acqua_contatore_seriale) && ! empty($data['serial'])) {
$patch['acqua_contatore_seriale'] = trim((string) $data['serial']);
}
if (Schema::hasColumn('unita_immobiliari', 'acqua_gateway_meta')) {
$patch['acqua_gateway_meta'] = array_filter([
'last_seen_at' => now()->toIso8601String(),
'battery' => $data['battery'] ?? null,
'rssi' => $data['rssi'] ?? null,
], static fn($value): bool => $value !== null);
}
if ($patch !== []) {
$unita->fill($patch);
$unita->save();
}
}
}