'w.WLL_NAME', 'bariera' => 'w.WLL_BARIERA', 'pump' => 'p.PUP_NAME', 'pump_no' => 'drp.DRS_SN_NUMBER', 'engine' => 'e.ENG_NAME', 'engine_no' => 'dre.DRS_SN_NUMBER', 'type' => 'w.WLL_SYS_TYPE', ]; public function __construct( private readonly PumpService $pumps, private readonly WellAssessmentService $assess, ) {} public function index(Request $request) { $department = $request->session()->get('department'); $search = trim((string) $request->query('q', '')); $status = (int) $request->query('status', 3); // domyślnie jak legacy [$sort, $dir] = $this->sortParams($request); $statusOptions = DB::connection('softspm')->table('sys_dictionaries') ->whereIn('DIC_ID', [3, 4]) ->orderBy('DIC_ID') ->pluck('DIC_POL_LANG', 'DIC_ID'); $wells = $this->buildQuery($department, $search, $status) ->when($sort !== null, fn ($q) => $q->orderBy(self::SORTABLE[$sort], $dir)) ->orderBy('w.WLL_NAME') ->paginate(20) ->withQueryString(); $this->attachOnOff($wells); return view('exploitation.wells.index', [ 'wells' => $wells, 'search' => $search, 'status' => $status, 'statusOptions' => $statusOptions, 'statusAll' => self::STATUS_ALL, 'department' => $department, ]); } /** Karta eksploatacyjna studni (migracja site401mark). */ public function show(int $drs) { $well = $this->wellDetails($drs); abort_if(! $well, 404); $onoff = $this->lastOnOff($drs); $m = $this->lastMeasurement($drs); // ostatni pomiar SYNDIS lub null $lpDr = $this->lpDr($drs); // [Lp, Dr] lub [null, null] $test = $this->pumpTest($well); // punkty próby pompy (kolekcja) // Typ studni (WLL_CAT_DIC_ID): 18=BASE, 19=OPT, 20=inne (bez oceny), inaczej FULL. $cat = (int) ($well->cat_dic_id ?? 0); $grey = $cat === 20; // "SPM - inne" -> brak ocen (jak legacy) // Krzywe z próby (skala BAZY = m³/h) - do obliczeń ocen/sprawności. $hCurve = $test->map(fn ($p) => (object) ['x' => $p->q, 'y' => $p->h])->values(); $etaCurve = $test->map(fn ($p) => (object) ['x' => $p->q, 'y' => $p->n])->values(); $hasTest = $test->count() > 1; $qn = (float) ($well->pump_qn ?? 0); // m³/h (obliczenia) $q = $m?->q !== null ? (float) $m->q : null; // m³/h (obliczenia) $hps = ($hasTest && $q !== null) ? $this->pumps->valueAt($hCurve, $q) : null; // do oceny H=f(Q) $eta = $grey ? null : $this->assess->efficiency($q, $m?->hd, $m?->p2, $m?->pe); $assessments = $grey ? null : [ 'q' => $this->assess->q($qn, $q), 'hfq' => $this->assess->hfq($hps, $m?->pp, $m?->hh), 'throttle' => $this->assess->throttling($m?->hd, $m?->p1, $m?->p2), 'loss' => $this->assess->lineLoss($m?->p1, $m?->pp, $lpDr[0], $lpDr[1]), ]; // WYŚWIETLANIE: poza katalogami jednostka przepływu = m³/min (= m³/h / 60). $toMin = fn (?float $v) => $v === null ? null : $v / 60; $hCurveDisp = $hCurve->map(fn ($p) => (object) ['x' => $p->x / 60, 'y' => $p->y])->values(); $etaCurveDisp = $etaCurve->map(fn ($p) => (object) ['x' => $p->x / 60, 'y' => $p->y])->values(); $opQ = $toMin($q); // Punkt pracy = wartości ZMIERZONE (nie rzutowane na krzywą próby): // - wysokość podnoszenia Hp = Pp - Hh (manometryczna, z pomiaru), // - sprawność = η obliczona (resETA). Odchyłka od krzywej = diagnostyka. $opH = ($m && $m->pp !== null && $m->hh !== null) ? ((float) $m->pp - (float) $m->hh) : null; $opEta = $eta; $ry = $this->assess->redYellowLimit($qn); $yg = $this->assess->yellowGreenLimit($qn); return view('exploitation.wells.show', [ 'well' => $well, 'onoff' => $onoff, 'measurement' => $m, 'qnMin' => $toMin($qn), // Qₙ w m³/min 'grey' => $grey, 'spmImage' => $this->spmImage($cat), 'diag' => $m?->dgn, 'assessments' => $assessments, 'energy' => $eta === null ? null : [ 'eta' => $eta, 'ry' => $ry, 'yg' => $yg, 'zone' => $this->assess->energyZone($eta, $ry, $yg), ], 'hPlot' => $hasTest ? $this->pumps->plot($hCurveDisp) : null, 'etaPlot' => $hasTest ? $this->pumps->plot($etaCurveDisp) : null, 'opQ' => $opQ, // punkt pracy Q w m³/min 'opH' => $opH, 'opEta' => $opEta, ]); } /** Parametry pracy układu (migracja site401inf) - pełny zestaw pomiarów + dane techniczne. */ public function params(int $drs) { $well = $this->wellDetails($drs); abort_if(! $well, 404); $m = $this->lastMeasurement($drs); // Dodatkowe parametry z detali (port getLastValue). $extra = (object) [ 'pb' => $this->lastDetailValue(225, $drs), 'throttle' => $this->lastDetailValue(730, $drs), 'energy_kwh' => $this->lastDetailValue(265, $drs), 'unit_energy' => $this->lastDetailValue(721, $drs), ]; // Parametry techniczne ze słownika WELL_TECHNICAL_PARAMETERS. $techParams = DB::connection('softspm')->table('sys_dictionaries') ->where('DIC_TYPE', 'WELL_TECHNICAL_PARAMETERS') ->orderBy('DIC_ID') ->get(['DIC_ID', 'DIC_POL_LANG']) ->map(fn ($d) => (object) [ 'label' => $d->DIC_POL_LANG, 'value' => $this->lastDetailValue((int) $d->DIC_ID, $drs), ]); [$lp, $dr] = $this->lpDr($drs); // Ostatnie zwierciadło statyczne >0 (jak site401bpow - osobne zapytanie). $hst = DB::connection('softspm')->table('rep_his_data_everyday') ->where('RHD_DRS_ID', $drs)->where('RHD_HST_VAL', '>', 0) ->orderByDesc('RHD_ID')->value('RHD_HST_VAL'); return view('exploitation.wells.params', [ 'well' => $well, 'measurement' => $m, 'extra' => $extra, 'techParams' => $techParams, 'lp' => $lp, 'dr' => $dr, 'mParam' => $this->lastDetailValue(251, $drs), 'category' => DB::connection('softspm')->table('sys_dictionaries')->where('DIC_ID', $well->cat_dic_id)->value('DIC_POL_LANG'), 'availableDate' => DB::connection('softspm')->table('seg_dev_resources')->where('DRS_ID', $drs)->value('DRS_AVAILABLE_DATE'), 'onoff' => $this->lastOnOff($drs), 'power' => $this->powerBalance($m, $lp, $dr, $hst === null ? null : (float) $hst), 'historyParams' => DB::connection('softspm')->table('sys_dictionaries') ->whereIn('DIC_ID', [200, 205, 210, 212, 215, 220, 245, 401, 402, 260, 270, 275, 280, 255]) ->orderBy('DIC_POL_LANG')->pluck('DIC_POL_LANG', 'DIC_ID'), ]); } /** Ręczny zapis pomiaru (migracja edycji „Modyfikuj" z site401inf). */ public function paramsStore(int $drs, Request $request) { $well = $this->wellDetails($drs); abort_if(! $well, 404); $request->validate(['date' => ['required', 'date']]); $num = fn ($k) => $request->filled($k) ? (float) str_replace(',', '.', (string) $request->input($k)) : null; $dt = $request->input('date').' '.($request->filled('time') ? $request->input('time').':00' : now()->format('H:i:s')); $user = $request->user()->USR_USERNAME; $qH = $num('q') !== null ? $num('q') * 60 : null; // wpis w m³/min -> baza m³/h DB::connection('softspm')->transaction(function () use ($drs, $num, $qH, $dt, $user) { DB::connection('softspm')->table('rep_his_data_everyday')->insert([ 'RHD_DRS_ID' => $drs, 'RHD_DATE' => $dt, 'RHD_Q_VAL' => $qH, 'RHD_HD_VAL' => $num('hd'), 'RHD_HST_VAL' => $num('hst'), 'RHD_HST0_VAL' => $num('hst0'), 'RHD_P1_VAL' => $num('p1'), 'RHD_P2_VAL' => $num('p2'), 'RHD_VM3_VAL' => $num('vm3'), 'RHD_WTEMP_VAL' => $num('wtemp'), 'RHD_PP_VAL' => $num('pp'), 'RHD_HH_VAL' => $num('hh'), 'RHD_PE_VAL' => $num('pe'), 'RHD_I_VAL' => $num('i'), 'RHD_U_VAL' => $num('u'), 'RHD_COS_VAL' => $num('cos'), 'RHD_WORK_TIME_VAL' => $num('work_time'), 'RHD_AUDIT_CD' => now()->toDateTimeString(), ]); $details = [ 200 => $qH, 205 => $num('hd'), 210 => $num('hst'), 212 => $num('hst0'), 215 => $num('p1'), 220 => $num('p2'), 225 => $num('pb'), 230 => $num('vm3'), 245 => $num('wtemp'), 401 => $num('pp'), 402 => $num('hh'), 730 => $num('throttle'), 260 => $num('pe'), 265 => $num('energy_kwh'), 270 => $num('i'), 275 => $num('u'), 280 => $num('cos'), 290 => $num('work_time'), 721 => $num('unit_energy'), ]; foreach ($details as $detail => $value) { if ($value === null) { continue; } DB::connection('softspm')->table('seg_dev_resources_det')->insert([ 'DRD_DRS_ID' => $drs, 'DRD_RESOURCES_DETAIL' => $detail, 'DRD_VALUE' => (string) $value, 'DRD_AUDIT_USR' => $user, 'DRD_AUDIT_DATE' => $dt, ]); } }); return redirect()->route('well-operation.params', $drs)->with('status', 'Zapisano ręczny pomiar.'); } /** * Bilans mocy układu (port nowszego site401bpow.php). Dane z ostatniego * pomiaru (rep_his_data_everyday) + Lp/Dr. Skala Q = m³/h (stała 0,002752 * ≈ moc hydrauliczna [kW] = Q·H/363). Zwraca składowe mocy lub null. */ private function powerBalance(?object $m, ?float $lp, ?float $dr, ?float $hst): ?array { if (! $m || $m->q === null || $m->pe === null) { return null; } $q = (float) $m->q; $pe = (float) $m->pe; $pp = (float) ($m->pp ?? 0); $hh = (float) ($m->hh ?? 0); $p1 = (float) ($m->p1 ?? 0); $p2 = (float) ($m->p2 ?? 0); $hstV = (float) ($hst ?? 0); $lpV = (float) ($lp ?? 0); $drV = (float) ($dr ?? 0); $k = 0.002752; $vel = $drV > 0 ? $q / (2826 * $drV * $drV) : 0.0; // prędkość w rurociągu $hct = ($vel * $vel) / 19.62; // wysokość prędkości $pod = $k * $q * (($pp - $hh) + $hct); $paz = $pe - $pod; $psl = $k * $q * ($pp - ($lpV + $p1)); $psa = $k * $q * ($p1 - $p2); $pp2 = $k * $q * ($p2 + $hct); $pst = $k * $q * $hstV; $pgh = $pod - ($pst + $pp2 + $psl + $psa); return [ 'pe' => $pe, 'pod' => $pod, 'paz' => $paz, 'psl' => $psl, 'psa' => $psa, 'pp2' => $pp2, 'pst' => $pst, 'pgh' => $pgh, 'sum' => $paz + $psl + $psa + $pp2 + $pst + $pgh, ]; } /** Pobranie aktualnych danych z SYNDIS (migracja site401mark&data=refresh + ajax_getdata4well). */ public function fetchData(int $drs, Request $request, SyndisService $syndis) { $well = $this->wellDetails($drs); abort_if(! $well, 404); try { $saved = $syndis->refreshWell($drs, $well->well_name, $request->user()->USR_USERNAME); return redirect()->route('well-operation.show', $drs)->with( $saved ? 'status' : 'error', $saved ? 'Pobrano aktualne dane z SYNDIS dla studni „'.$well->well_name.'".' : 'Studnia wyłączona lub brak aktualnych danych w SYNDIS.' ); } catch (\Throwable $e) { return redirect()->route('well-operation.show', $drs) ->with('error', 'Nie udało się pobrać danych z SYNDIS: '.$e->getMessage()); } } /** * Lista eksploatacyjna - jedno zapytanie z joinami (pompa/silnik), stan * ON/OFF dociągany osobno dla bieżącej strony. Wierne site401.php. */ private function buildQuery(?string $department, string $search, int $status) { return DB::connection('softspm') ->table('seg_wells as w') ->leftJoin('seg_dev_resources as drw', function ($j) { $j->on('drw.DRS_DEV_ID', '=', 'w.WLL_ID')->where('drw.DRS_DEV_TYPE', '=', 12); }) ->leftJoin('seg_well_pump_eng_rel as rel', 'rel.RWL_WLL_ID', '=', 'w.WLL_ID') ->leftJoin('seg_dev_resources as drp', function ($j) { $j->on('drp.DRS_ID', '=', 'rel.RWL_PUP_DRS_ID')->where('drp.DRS_DEV_TYPE', '=', 10); }) ->leftJoin('seg_pumps as p', 'p.PUP_ID', '=', 'drp.DRS_DEV_ID') ->leftJoin('seg_dev_resources as dre', function ($j) { $j->on('dre.DRS_ID', '=', 'rel.RWL_ENG_DRS_ID')->where('dre.DRS_DEV_TYPE', '=', 11); }) ->leftJoin('seg_engines as e', 'e.ENG_ID', '=', 'dre.DRS_DEV_ID') ->where('drw.DRS_DEPARTMENT', $department) ->when($search !== '', fn ($q) => $q->where('w.WLL_NAME', 'like', '%'.$search.'%')) ->when($status !== self::STATUS_ALL, fn ($q) => $q->where('drw.DRS_STATUS_DIC_ID', $status)) ->select([ 'drw.DRS_ID as drs_id', 'w.WLL_NAME as well_name', 'w.WLL_BARIERA as bariera', 'p.PUP_NAME as pump_name', 'drp.DRS_SN_NUMBER as pump_no', 'e.ENG_NAME as engine_name', 'dre.DRS_SN_NUMBER as engine_no', 'w.WLL_SYS_TYPE as sys_type', ]); } /** Stan ON/OFF (DRD_RESOURCES_DETAIL=507) tylko dla studni z bieżącej strony. */ private function attachOnOff(LengthAwarePaginator $wells): void { $ids = collect($wells->items())->pluck('drs_id')->filter()->values()->all(); $values = empty($ids) ? collect() : DB::connection('softspm') ->table('seg_dev_resources_det as d') ->joinSub( DB::connection('softspm')->table('seg_dev_resources_det') ->select('DRD_DRS_ID', DB::raw('MAX(DRD_ID) as max_id')) ->where('DRD_RESOURCES_DETAIL', 507) ->whereIn('DRD_DRS_ID', $ids) ->groupBy('DRD_DRS_ID'), 'mx', 'mx.max_id', '=', 'd.DRD_ID' ) ->pluck('d.DRD_VALUE', 'd.DRD_DRS_ID'); foreach ($wells->items() as $row) { $row->onoff = $values[$row->drs_id] ?? null; } } /** Podstawowe dane studni + układu pompowego (część site401mark). */ private function wellDetails(int $drs): ?object { return DB::connection('softspm') ->table('seg_dev_resources as drw') ->join('seg_wells as w', 'w.WLL_ID', '=', 'drw.DRS_DEV_ID') ->leftJoin('seg_well_pump_eng_rel as rel', 'rel.RWL_WLL_ID', '=', 'w.WLL_ID') ->leftJoin('seg_dev_resources as drp', function ($j) { $j->on('drp.DRS_ID', '=', 'rel.RWL_PUP_DRS_ID')->where('drp.DRS_DEV_TYPE', '=', 10); }) ->leftJoin('seg_pumps as p', 'p.PUP_ID', '=', 'drp.DRS_DEV_ID') ->leftJoin('seg_dev_resources as dre', function ($j) { $j->on('dre.DRS_ID', '=', 'rel.RWL_ENG_DRS_ID')->where('dre.DRS_DEV_TYPE', '=', 11); }) ->leftJoin('seg_engines as e', 'e.ENG_ID', '=', 'dre.DRS_DEV_ID') ->where('drw.DRS_ID', $drs) ->where('drw.DRS_DEV_TYPE', 12) ->select([ 'drw.DRS_ID as drs_id', 'w.WLL_ID as well_id', 'w.WLL_NAME as well_name', 'w.WLL_BARIERA as bariera', 'w.WLL_SYS_TYPE as sys_type', 'w.WLL_CAT_DIC_ID as cat_dic_id', 'p.PUP_ID as pump_id', 'p.PUP_NAME as pump_name', 'drp.DRS_SN_NUMBER as pump_no', 'p.PUP_Q_N as pump_qn', 'e.ENG_NAME as engine_name', 'dre.DRS_SN_NUMBER as engine_no', 'e.ENG_VOLTAGE as engine_voltage', ]) ->first(); } /** Ostatni pomiar SYNDIS (rep_his_data_everyday) dla studni lub null. */ private function lastMeasurement(int $drs): ?object { $r = DB::connection('softspm')->table('rep_his_data_everyday') ->where('RHD_DRS_ID', $drs) ->orderByDesc('RHD_ID') ->first(); if (! $r) { return null; } return (object) [ 'q' => $r->RHD_Q_VAL, 'hd' => $r->RHD_HD_VAL, 'hst' => $r->RHD_HST_VAL, 'hst0' => $r->RHD_HST0_VAL, 'p1' => $r->RHD_P1_VAL, 'p2' => $r->RHD_P2_VAL, 'pp' => $r->RHD_PP_VAL, 'hh' => $r->RHD_HH_VAL, 'pe' => $r->RHD_PE_VAL, 'i' => $r->RHD_I_VAL, 'u' => $r->RHD_U_VAL, 'cos' => $r->RHD_COS_VAL, 'vm3' => $r->RHD_VM3_VAL, 'wtemp' => $r->RHD_WTEMP_VAL, 'work_time' => $r->RHD_WORK_TIME_VAL, 'dgn' => $r->RHD_DGN_VAL, 'date' => $r->RHD_DATE, ]; } /** * Lp (głębokość zabudowy pompy, detal 240) i Dr (średnica rurociągu, * detal 235/1000). Zastępuje funkcję składowaną MySQL getLastValue * własnym zapytaniem (optymalizacja - bez zależności od procedur w bazie). */ private function lpDr(int $drs): array { $lp = $this->lastDetailValue(240, $drs); $dr = $this->lastDetailValue(235, $drs); return [ $lp !== null ? (float) $lp : null, $dr !== null ? (float) $dr / 1000 : null, ]; } /** * Ostatnia wartość detalu zasobu (port funkcji składowanej getLastValue): * DRD_VALUE z seg_dev_resources_det dla danego detalu i zasobu, po * najpóźniejszej dacie audytu (rozstrzyganie po DRD_ID). */ private function lastDetailValue(int $detail, int $drs): ?string { return DB::connection('softspm')->table('seg_dev_resources_det') ->where('DRD_RESOURCES_DETAIL', $detail) ->where('DRD_DRS_ID', $drs) ->orderByDesc('DRD_AUDIT_DATE') ->orderByDesc('DRD_ID') ->value('DRD_VALUE'); } /** * Punkty próby pompy ze stacji prób (tst_tests + tst_test_details): * q=TSD_VAL_04, h=TSD_VAL_08, p=TSD_VAL_14, n=TSD_VAL_18 (skala m³/h, bez /60). * Dobór próby: po PUP_ID + nr fabrycznych pompy/silnika (fallback bez silnika). */ private function pumpTest(object $well): Collection { if (empty($well->pump_id)) { return collect(); } $base = DB::connection('softspm')->table('tst_tests') ->where('TST_PUP_ID', $well->pump_id) ->where('TST_PUP_SN_NO', $well->pump_no); $testId = (clone $base)->where('TST_ENG_SN_NO', $well->engine_no)->max('TST_ID') ?? $base->max('TST_ID'); if (! $testId) { return collect(); } return DB::connection('softspm')->table('tst_test_details') ->where('TSD_TST_ID', $testId) ->orderBy('TSD_MSR_STEP') ->get() ->map(fn ($d) => (object) [ 'q' => (float) $d->TSD_VAL_04, 'h' => (float) $d->TSD_VAL_08, 'p' => (float) $d->TSD_VAL_14, 'n' => (float) $d->TSD_VAL_18, ]) ->filter(fn ($p) => $p->q > 0) ->values(); } /** Plik schematu SPM wg kategorii studni (18=BASE, 19=OPT, inaczej FULL). */ private function spmImage(int $cat): string { return match ($cat) { 18 => 'images/spm_base.png', 19 => 'images/spm_opt.png', default => 'images/spm_full.png', }; } /** Ostatni wpis ON/OFF dla studni (wartość, data, powód wyłączenia). */ private function lastOnOff(int $drs): ?object { $row = DB::connection('softspm')->table('seg_dev_resources_det') ->where('DRD_RESOURCES_DETAIL', 507) ->where('DRD_DRS_ID', $drs) ->orderByDesc('DRD_ID') ->first(); if ($row && ! empty($row->DRD_DESCRIPTION)) { $row->reason = DB::connection('softspm')->table('sys_dictionaries') ->where('DIC_ID', $row->DRD_DESCRIPTION) ->value('DIC_POL_LANG'); } return $row; } }