Initial commit
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Concerns\SortsLists;
|
||||
use App\Models\Engine;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\Pump;
|
||||
use App\Services\PumpService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Katalog agregatow (legacy site205 / site205E) - "agregat" = pompa + przypisane
|
||||
* silniki (seg_pump_engine_rel). Uklad lista -> szczegoly (jak pozostale katalogi).
|
||||
*/
|
||||
class AggregateController extends Controller
|
||||
{
|
||||
use SortsLists;
|
||||
|
||||
private const MCF_ALL = 100;
|
||||
|
||||
/** Sortowalne kolumny listy: klucz nagłówka => kolumna/alias SQL. */
|
||||
private const SORTABLE = [
|
||||
'name' => 'PUP_NAME',
|
||||
'flow' => 'PUP_Q_N',
|
||||
'mcf' => 'm.MCF_NAME',
|
||||
'engines' => 'engines_count',
|
||||
];
|
||||
|
||||
public function __construct(private readonly PumpService $pumps) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$by = $request->query('by', 'name');
|
||||
$q = trim((string) $request->query('q', ''));
|
||||
$mcf = (int) $request->query('mcf', self::MCF_ALL);
|
||||
|
||||
[$sort, $dir] = $this->sortParams($request);
|
||||
|
||||
$aggregates = Pump::query()
|
||||
->visible()->with('manufacturer')->where('PUP_Q_N', '>', 0)
|
||||
->select('seg_pumps.*')
|
||||
->selectSub(
|
||||
DB::connection('softspm')->table('seg_pump_engine_rel')
|
||||
->whereColumn('RPE_PUP_ID', 'seg_pumps.PUP_ID')->selectRaw('count(*)'),
|
||||
'engines_count'
|
||||
)
|
||||
->when($q !== '' && $by === 'name', fn ($w) => $w->where('PUP_NAME', 'like', '%'.$q.'%'))
|
||||
->when($q !== '' && $by === 'flow', function ($w) use ($q) {
|
||||
$v = (float) str_replace(',', '.', $q);
|
||||
$w->whereBetween('PUP_Q_N', [$v - $v * 0.1, $v + $v * 0.1]);
|
||||
})
|
||||
->when($mcf !== self::MCF_ALL, fn ($w) => $w->where('PUP_MCF_ID', $mcf))
|
||||
// Sortowanie po producencie wymaga joinu (relacja nie jest w FROM).
|
||||
->when($sort === 'mcf', fn ($w) => $w
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'seg_pumps.PUP_MCF_ID'))
|
||||
->when($sort !== null, fn ($w) => $w->orderBy(self::SORTABLE[$sort], $dir))
|
||||
->orderBy('PUP_Q_N')->orderBy('PUP_NAME')
|
||||
->paginate(20)->withQueryString();
|
||||
|
||||
return view('catalog.aggregates.index', [
|
||||
'aggregates' => $aggregates,
|
||||
'manufacturers' => Manufacturer::orderBy('MCF_NAME')->pluck('MCF_NAME', 'MCF_ID'),
|
||||
'by' => $by, 'q' => $q, 'mcf' => $mcf, 'mcfAll' => self::MCF_ALL,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request, Pump $pump)
|
||||
{
|
||||
$pump->load('manufacturer');
|
||||
|
||||
$h = $this->pumps->curve($pump, 'H');
|
||||
$eta = $this->pumps->curve($pump, 'ETA');
|
||||
$hDrawable = $this->pumps->curveDrawable($h, 0);
|
||||
$etaDrawable = $this->pumps->curveDrawable($eta, 1);
|
||||
|
||||
$engines = $this->assignedEngines($pump->PUP_ID);
|
||||
$engineId = (int) $request->query('engine') ?: (int) ($engines->first()->ENG_ID ?? 0);
|
||||
$engine = $engineId && $engines->firstWhere('ENG_ID', $engineId)
|
||||
? Engine::with('manufacturer')->find($engineId)
|
||||
: null;
|
||||
|
||||
return view('catalog.aggregates.show', [
|
||||
'pump' => $pump,
|
||||
'engines' => $engines,
|
||||
'engine' => $engine,
|
||||
'h' => $h, 'eta' => $eta,
|
||||
'hDrawable' => $hDrawable, 'etaDrawable' => $etaDrawable,
|
||||
'hPlot' => $hDrawable ? $this->pumps->plot($h) : null,
|
||||
'etaPlot' => $etaDrawable ? $this->pumps->plot($eta) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Pump $pump)
|
||||
{
|
||||
$allEngines = Engine::query()->visible()->with('manufacturer')
|
||||
->orderBy('ENG_POWER')->orderBy('ENG_NAME')->get();
|
||||
|
||||
$assigned = DB::connection('softspm')->table('seg_pump_engine_rel')
|
||||
->where('RPE_PUP_ID', $pump->PUP_ID)->pluck('RPE_ENG_ID')->all();
|
||||
|
||||
return view('catalog.aggregates.edit', [
|
||||
'pump' => $pump->load('manufacturer'),
|
||||
'allEngines' => $allEngines,
|
||||
'assigned' => array_map('intval', $assigned),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Pump $pump)
|
||||
{
|
||||
$engineIds = collect($request->input('engines', []))->map(fn ($v) => (int) $v)->filter()->unique();
|
||||
|
||||
DB::connection('softspm')->transaction(function () use ($pump, $engineIds) {
|
||||
DB::connection('softspm')->table('seg_pump_engine_rel')->where('RPE_PUP_ID', $pump->PUP_ID)->delete();
|
||||
foreach ($engineIds as $eid) {
|
||||
DB::connection('softspm')->table('seg_pump_engine_rel')
|
||||
->insert(['RPE_PUP_ID' => $pump->PUP_ID, 'RPE_ENG_ID' => $eid]);
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('aggregates.show', $pump)
|
||||
->with('status', 'Zaktualizowano silniki dla pompy "'.$pump->PUP_NAME.'".');
|
||||
}
|
||||
|
||||
private function assignedEngines(int $pumpId)
|
||||
{
|
||||
return DB::connection('softspm')->table('seg_pump_engine_rel as r')
|
||||
->join('seg_engines as e', 'e.ENG_ID', '=', 'r.RPE_ENG_ID')
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'e.ENG_MCF_ID')
|
||||
->where('r.RPE_PUP_ID', $pumpId)
|
||||
->select('e.ENG_ID', 'e.ENG_NAME', 'e.ENG_VOLTAGE', 'e.ENG_POWER', 'e.ENG_ETA', 'm.MCF_NAME')
|
||||
->orderBy('e.ENG_POWER')->orderBy('e.ENG_NAME')->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\PumpService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Wybór agregatu pompowego (legacy site201 "Wysokość podnoszenia dla posiadanych
|
||||
* typów pomp"). Narzędzie doboru: użytkownik podaje wydajność Q (i opcjonalnie
|
||||
* wysokość podnoszenia H), system zwraca pompy obejmujące to Q wraz z H(Q)/η(Q),
|
||||
* listę zestawów pompa+silnik posortowaną malejąco po sprawności oraz wykres
|
||||
* nałożonych krzywych H. Klik wiersza -> szczegóły zestawu (aggregates.show).
|
||||
*
|
||||
* Szczegół zestawu = istniejący ekran agregatu (site201det był jego duplikatem),
|
||||
* więc nie powielamy - kierujemy do aggregates.show?pump=X&engine=Y.
|
||||
*/
|
||||
class AggregateSelectionController extends Controller
|
||||
{
|
||||
public function __construct(private readonly PumpService $pumps) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$qIn = $request->query('q');
|
||||
$hIn = $request->query('h');
|
||||
$q = $this->toFloat($qIn);
|
||||
$h = $this->toFloat($hIn);
|
||||
|
||||
$searched = $q !== null && $q > 0;
|
||||
$matches = $searched ? $this->pumps->matchingAggregates($q, $h) : collect();
|
||||
|
||||
[$rows, $chart] = $matches->isNotEmpty()
|
||||
? [$this->buildRows($matches), $this->buildChart($matches)]
|
||||
: [collect(), null];
|
||||
|
||||
return view('catalog.aggregate-selection.index', [
|
||||
'q' => $qIn, // surowe (do ponownego wyświetlenia w polu)
|
||||
'h' => $hIn,
|
||||
'qVal' => $q, // liczbowe (do nagłówka Q_sz)
|
||||
'rows' => $rows,
|
||||
'chart' => $chart,
|
||||
'searched' => $searched,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rozwija dopasowane pompy na wiersze pompa+silnik. Silniki pompy wg mocy
|
||||
* (ENG_POWER) - jak legacy. leftJoin: pompa bez silnika wciąż daje 1 wiersz.
|
||||
*/
|
||||
private function buildRows($matches)
|
||||
{
|
||||
$engines = DB::connection('softspm')->table('seg_pump_engine_rel as r')
|
||||
->leftJoin('seg_engines as e', 'e.ENG_ID', '=', 'r.RPE_ENG_ID')
|
||||
->whereIn('r.RPE_PUP_ID', $matches->pluck('pump.PUP_ID')->all())
|
||||
->orderBy('e.ENG_POWER')
|
||||
->select('r.RPE_PUP_ID', 'e.ENG_ID', 'e.ENG_NAME', 'e.ENG_POWER', 'e.ENG_VOLTAGE')
|
||||
->get()
|
||||
->groupBy('RPE_PUP_ID');
|
||||
|
||||
$rows = collect();
|
||||
$lp = 1;
|
||||
foreach ($matches as $m) {
|
||||
$pumpEngines = $engines->get($m->pump->PUP_ID) ?: collect([null]);
|
||||
foreach ($pumpEngines as $e) {
|
||||
$rows->push((object) [
|
||||
'lp' => $lp++,
|
||||
'pump_id' => $m->pump->PUP_ID,
|
||||
'pump_name' => $m->pump->PUP_NAME,
|
||||
'engine_id' => $e->ENG_ID ?? null,
|
||||
'engine_name' => $e->ENG_NAME ?? null,
|
||||
'q_n' => $m->pump->PUP_Q_N,
|
||||
'h_val' => $m->h_val,
|
||||
'eta_val' => $m->eta_val,
|
||||
'power' => $e->ENG_POWER ?? null,
|
||||
'voltage' => $e->ENG_VOLTAGE ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dane wykresu nałożonych krzywych H (wspólne osie). xMax = najszersza
|
||||
* krzywa, yMax = najwyższa (zaokrąglona w górę do 10, jak legacy).
|
||||
*/
|
||||
private function buildChart($matches): ?array
|
||||
{
|
||||
$curves = $matches->filter(fn ($m) => $m->plot)
|
||||
->map(fn ($m) => ['name' => $m->pump->PUP_NAME, 'plot' => $m->plot])
|
||||
->values();
|
||||
|
||||
if ($curves->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'curves' => $curves,
|
||||
'xMax' => (float) $curves->max(fn ($c) => $c['plot']['xMax']),
|
||||
'yMax' => max(10.0, ceil($curves->max(fn ($c) => $c['plot']['yMax']) / 10) * 10),
|
||||
];
|
||||
}
|
||||
|
||||
/** Pole tekstowe -> float (przecinek dziesiętny), puste -> null. */
|
||||
private function toFloat($v): ?float
|
||||
{
|
||||
if ($v === null || trim((string) $v) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (float) str_replace(',', '.', (string) $v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* Logowanie SoftSPM_2 na istniejącej bazie `sys_users` (database-first).
|
||||
*
|
||||
* Strategia haseł (przejściowa):
|
||||
* - Legacy trzyma USR_PASSWORD = md5(hasło).
|
||||
* - Akceptujemy logowanie po MD5 (kompatybilność) ORAZ po bcrypt.
|
||||
* - Po udanym logowaniu MD5, jeśli kolumna USR_PASSWORD pomieści bcrypt
|
||||
* (>=60 znaków) i włączony jest flag SOFTSPM_REHASH - przepisujemy hash
|
||||
* na bcrypt. Domyślnie WYŁĄCZONE, żeby nie uszkodzić zbyt krótkiej kolumny.
|
||||
* Decyzja o rozszerzeniu kolumny / osobnej kolumnie do ustalenia.
|
||||
*/
|
||||
class LoginController extends Controller
|
||||
{
|
||||
/** Pokaż formularz logowania. */
|
||||
public function show()
|
||||
{
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
/** Obsłuż próbę logowania. */
|
||||
public function attempt(Request $request)
|
||||
{
|
||||
// Faza testów: oddział może być wymuszony w configu (pole ukryte w logowaniu).
|
||||
$forcedDepartment = config('softspm.login_department');
|
||||
|
||||
$rules = [
|
||||
'username' => ['required', 'string', 'max:100'],
|
||||
'password' => ['required', 'string'],
|
||||
'lang' => ['nullable', 'in:pl,en,de'],
|
||||
];
|
||||
if (! $forcedDepartment) {
|
||||
$rules['department'] = ['required', 'string', Rule::in(config('softspm.departments'))];
|
||||
}
|
||||
$credentials = $request->validate($rules);
|
||||
|
||||
$department = $forcedDepartment ?: $credentials['department'];
|
||||
|
||||
$user = User::query()
|
||||
->where('USR_USERNAME', $credentials['username'])
|
||||
->where('USR_STATUS', '1')
|
||||
->first();
|
||||
|
||||
if (! $user || ! $this->passwordMatches($credentials['password'], $user->USR_PASSWORD)) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => 'Nieprawidłowy login lub hasło.',
|
||||
]);
|
||||
}
|
||||
|
||||
// Opcjonalna migracja MD5 -> bcrypt (patrz docblock klasy).
|
||||
$this->maybeRehash($user, $credentials['password']);
|
||||
|
||||
Auth::login($user);
|
||||
$request->session()->regenerate();
|
||||
|
||||
// Oddział filtrujący listy obiektów (odpowiednik legacy 'syndis_eq_stock').
|
||||
$request->session()->put('department', $department);
|
||||
|
||||
// Zapamiętaj wybrany język UI (i18n w kolejnym etapie).
|
||||
if (! empty($credentials['lang'])) {
|
||||
$request->session()->put('locale', $credentials['lang']);
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard'));
|
||||
}
|
||||
|
||||
/** Wyloguj. */
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Weryfikacja hasła: bcrypt (nowe) lub md5 (legacy).
|
||||
*/
|
||||
private function passwordMatches(string $plain, ?string $stored): bool
|
||||
{
|
||||
if (empty($stored)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hash bcrypt zaczyna się od $2y$ / $2a$ - sprawdź najpierw.
|
||||
if (str_starts_with($stored, '$2')) {
|
||||
return Hash::check($plain, $stored);
|
||||
}
|
||||
|
||||
// Legacy: md5 (32 znaki hex). hash_equals chroni przed timing-attack.
|
||||
return hash_equals(strtolower($stored), md5($plain));
|
||||
}
|
||||
|
||||
/**
|
||||
* Po logowaniu MD5 przepisz hash na bcrypt - tylko gdy bezpiecznie.
|
||||
*/
|
||||
private function maybeRehash(User $user, string $plain): void
|
||||
{
|
||||
if (! config('softspm.rehash_passwords', false)) {
|
||||
return;
|
||||
}
|
||||
if (str_starts_with((string) $user->USR_PASSWORD, '$2')) {
|
||||
return; // już bcrypt
|
||||
}
|
||||
|
||||
$user->USR_PASSWORD = Hash::make($plain);
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Concerns\SortsLists;
|
||||
use App\Http\Requests\StoreCableRequest;
|
||||
use App\Http\Requests\UpdateCableRequest;
|
||||
use App\Models\Equipment;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Services\EquipmentFileService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Katalog kabli - pełny CRUD (legacy site206 / site206A / site206E / site206inf).
|
||||
* Dane w seg_equipments (EQU_TYPE_ID=13) + załączniki BLOB w seg_equipments_files.
|
||||
* Usuwanie logiczne (EQU_STATUS='0'); blokowane gdy kabel jest na stanie
|
||||
* (seg_equipments_stock) - jak legacy. Newest methods: Eloquent + Form Requests.
|
||||
*/
|
||||
class CableController extends Controller
|
||||
{
|
||||
use SortsLists;
|
||||
|
||||
/** EQU_TYPE_ID kabli (sys_dictionaries). */
|
||||
private const TYPE_CABLE = 13;
|
||||
|
||||
/** Producent „Wszyscy" (jak legacy: 100). */
|
||||
private const MCF_ALL = 100;
|
||||
|
||||
/** Sortowalne kolumny listy: klucz nagłówka => kolumna SQL. */
|
||||
private const SORTABLE = [
|
||||
'name' => 'EQU_NAME',
|
||||
'mcf' => 'm.MCF_NAME',
|
||||
];
|
||||
|
||||
public function __construct(private readonly EquipmentFileService $files) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$q = trim((string) $request->query('q', ''));
|
||||
$mcf = (int) $request->query('mcf', self::MCF_ALL);
|
||||
|
||||
[$sort, $dir] = $this->sortParams($request);
|
||||
|
||||
$cables = Equipment::query()
|
||||
->visible()->ofType(self::TYPE_CABLE)->with('manufacturer')
|
||||
->when($q !== '', fn ($w) => $w->where('EQU_NAME', 'like', '%'.$q.'%'))
|
||||
->when($mcf !== self::MCF_ALL, fn ($w) => $w->where('EQU_MCF_ID', $mcf))
|
||||
->when($sort === 'mcf', fn ($w) => $w
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'seg_equipments.EQU_MCF_ID')
|
||||
->select('seg_equipments.*'))
|
||||
->when($sort !== null, fn ($w) => $w->orderBy(self::SORTABLE[$sort], $dir))
|
||||
->orderBy('EQU_NAME')
|
||||
->paginate(20)
|
||||
->withQueryString();
|
||||
|
||||
return view('catalog.cables.index', [
|
||||
'cables' => $cables,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'q' => $q,
|
||||
'mcf' => $mcf,
|
||||
'mcfAll' => self::MCF_ALL,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('catalog.cables.create', ['manufacturers' => $this->manufacturers()]);
|
||||
}
|
||||
|
||||
public function store(StoreCableRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
|
||||
$cable = Equipment::create([
|
||||
'EQU_NAME' => $data['name'],
|
||||
'EQU_MCF_ID' => $data['manufacturer_id'],
|
||||
'EQU_DESCRIPTION' => $data['description'] ?? null,
|
||||
'EQU_TYPE_ID' => self::TYPE_CABLE,
|
||||
'EQU_STATUS' => '1',
|
||||
]);
|
||||
|
||||
$this->saveFiles($request, $cable->EQU_ID);
|
||||
|
||||
return redirect()->route('cables.show', $cable)
|
||||
->with('status', 'Dodano typ kabla „'.$cable->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
public function show(Equipment $cable)
|
||||
{
|
||||
abort_unless((int) $cable->EQU_TYPE_ID === self::TYPE_CABLE, 404);
|
||||
$cable->load('manufacturer', 'files');
|
||||
|
||||
return view('catalog.cables.show', [
|
||||
'cable' => $cable,
|
||||
'document' => $cable->files->first(fn ($f) => ! $f->isImage()),
|
||||
'image' => $cable->files->first(fn ($f) => $f->isImage()),
|
||||
'inStock' => $this->inStock($cable->EQU_ID),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Equipment $cable)
|
||||
{
|
||||
abort_unless((int) $cable->EQU_TYPE_ID === self::TYPE_CABLE, 404);
|
||||
$cable->load('manufacturer', 'files');
|
||||
|
||||
return view('catalog.cables.edit', [
|
||||
'cable' => $cable,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'document' => $cable->files->first(fn ($f) => ! $f->isImage()),
|
||||
'image' => $cable->files->first(fn ($f) => $f->isImage()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateCableRequest $request, Equipment $cable)
|
||||
{
|
||||
abort_unless((int) $cable->EQU_TYPE_ID === self::TYPE_CABLE, 404);
|
||||
$data = $request->validated();
|
||||
|
||||
$cable->update([
|
||||
'EQU_NAME' => $data['name'],
|
||||
'EQU_MCF_ID' => $data['manufacturer_id'],
|
||||
'EQU_DESCRIPTION' => $data['description'] ?? null,
|
||||
]);
|
||||
|
||||
$this->saveFiles($request, $cable->EQU_ID);
|
||||
|
||||
return redirect()->route('cables.show', $cable)
|
||||
->with('status', 'Zaktualizowano typ kabla „'.$cable->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
public function destroy(Equipment $cable)
|
||||
{
|
||||
abort_unless((int) $cable->EQU_TYPE_ID === self::TYPE_CABLE, 404);
|
||||
|
||||
// Legacy: nie usuwaj, gdy kabel jest na stanie (seg_equipments_stock).
|
||||
if ($this->inStock($cable->EQU_ID)) {
|
||||
return redirect()->route('cables.show', $cable)
|
||||
->with('error', 'Nie można usunąć - kabel jest powiązany ze stanem magazynowym.');
|
||||
}
|
||||
|
||||
$cable->update(['EQU_STATUS' => '0']); // usuwanie logiczne
|
||||
|
||||
return redirect()->route('cables.index')
|
||||
->with('status', 'Usunięto typ kabla „'.$cable->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
/** Zapis przesłanych plików (dokumentacja + zdjęcie), jeśli są. */
|
||||
private function saveFiles(Request $request, int $equId): void
|
||||
{
|
||||
$user = $request->user()->USR_USERNAME;
|
||||
if ($request->hasFile('catalogue_file')) {
|
||||
$this->files->store($equId, $request->file('catalogue_file'), $user);
|
||||
}
|
||||
if ($request->hasFile('image_file')) {
|
||||
$this->files->store($equId, $request->file('image_file'), $user);
|
||||
}
|
||||
}
|
||||
|
||||
private function inStock(int $equId): bool
|
||||
{
|
||||
return DB::connection('softspm')->table('seg_equipments_stock')
|
||||
->where('SES_EQU_ID', $equId)->exists();
|
||||
}
|
||||
|
||||
private function manufacturers()
|
||||
{
|
||||
return Manufacturer::orderBy('MCF_NAME')->pluck('MCF_NAME', 'MCF_ID');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Concerns;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Wspólne wyznaczanie parametrów sortowania list (nagłówki <x-sortable-th>).
|
||||
* Kontroler udostępnia whitelistę kolumn przez stałą self::SORTABLE
|
||||
* (klucz nagłówka => kolumna SQL). Zapobiega wstrzyknięciu dowolnej kolumny
|
||||
* do ORDER BY - bierzemy tylko klucze z whitelisty.
|
||||
*/
|
||||
trait SortsLists
|
||||
{
|
||||
/**
|
||||
* @return array{0: ?string, 1: string} [klucz sortowania lub null, kierunek 'asc'|'desc']
|
||||
*/
|
||||
protected function sortParams(Request $request): array
|
||||
{
|
||||
$sort = $request->query('sort');
|
||||
$dir = $request->query('dir') === 'desc' ? 'desc' : 'asc';
|
||||
|
||||
if (! is_string($sort) || ! array_key_exists($sort, static::SORTABLE)) {
|
||||
return [null, $dir];
|
||||
}
|
||||
|
||||
return [$sort, $dir];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Pulpit / Mapa Obiektów. Nanosi studnie jako kropki na płaszczyznę wg
|
||||
* LOKALNYCH współrzędnych geodezyjnych (seg_wells.WLL_LATITUDE/WLL_LONGITUDE -
|
||||
* siatka kopalniana w metrach, NIE WGS84). Normalizacja min-max do obszaru mapy
|
||||
* daje dokładny względny układ pola studni (bez internetu - air-gap OK).
|
||||
*
|
||||
* Kolor kropki: zielony = pompa pracuje (ON), czerwony = nie pracuje (OFF).
|
||||
*/
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
/** Margines obszaru mapy (%), żeby kropki nie dotykały krawędzi. */
|
||||
private const PAD = 6;
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$department = $request->session()->get('department');
|
||||
|
||||
$rows = DB::connection('softspm')->table('seg_wells as w')
|
||||
->join('seg_dev_resources as drw', function ($j) {
|
||||
$j->on('drw.DRS_DEV_ID', '=', 'w.WLL_ID')->where('drw.DRS_DEV_TYPE', '=', 12);
|
||||
})
|
||||
->where('drw.DRS_DEPARTMENT', $department)
|
||||
->where('drw.DRS_STATUS_DIC_ID', 3) // w eksploatacji
|
||||
->select('drw.DRS_ID as drs_id', 'w.WLL_NAME as name', 'w.WLL_LATITUDE as lat', 'w.WLL_LONGITUDE as lon')
|
||||
->get();
|
||||
|
||||
// Parsowanie współrzędnych (przecinek lub kropka dziesiętna), odrzucenie błędnych.
|
||||
$num = function ($v) {
|
||||
$s = str_replace([',', ' '], ['.', ''], (string) $v);
|
||||
return is_numeric($s) ? (float) $s : null;
|
||||
};
|
||||
$wells = $rows->map(function ($r) use ($num) {
|
||||
$r->lat = $num($r->lat);
|
||||
$r->lon = $num($r->lon);
|
||||
return $r;
|
||||
})->filter(fn ($r) => $r->lat !== null && $r->lon !== null && $r->lat > 0 && $r->lon > 0)->values();
|
||||
|
||||
// Stan ON/OFF dla naniesionych studni (DRD_RESOURCES_DETAIL=507).
|
||||
$onoff = $this->onOff($wells->pluck('drs_id')->all());
|
||||
|
||||
// Normalizacja do obszaru mapy. Zakres liczony z PERCENTYLI (2-98%), żeby
|
||||
// pojedyncze studnie ze skrajnymi współrzędnymi nie rozciągały skali i nie
|
||||
// zwijały reszty w jeden punkt. Pozycje przycięte (clamp) do obszaru.
|
||||
$minLat = $this->percentile($wells->pluck('lat')->all(), 2);
|
||||
$maxLat = $this->percentile($wells->pluck('lat')->all(), 98);
|
||||
$minLon = $this->percentile($wells->pluck('lon')->all(), 2);
|
||||
$maxLon = $this->percentile($wells->pluck('lon')->all(), 98);
|
||||
$latRange = max(1e-6, $maxLat - $minLat);
|
||||
$lonRange = max(1e-6, $maxLon - $minLon);
|
||||
$span = 100 - 2 * self::PAD;
|
||||
$clamp = fn ($v) => max(self::PAD, min(100 - self::PAD, $v));
|
||||
|
||||
$points = $wells->map(function ($r) use ($minLat, $maxLat, $minLon, $lonRange, $latRange, $span, $clamp, $onoff) {
|
||||
return (object) [
|
||||
'drs_id' => $r->drs_id,
|
||||
'name' => $r->name,
|
||||
// X = długość (wschód), Y = szerokość (północ, odwrócona - północ u góry).
|
||||
'left' => round($clamp(self::PAD + (($r->lon - $minLon) / $lonRange) * $span), 3),
|
||||
'top' => round($clamp(self::PAD + (($maxLat - $r->lat) / $latRange) * $span), 3),
|
||||
'on' => ($onoff[$r->drs_id] ?? null) === 'ON',
|
||||
];
|
||||
});
|
||||
|
||||
return view('dashboard', [
|
||||
'points' => $points,
|
||||
'onCount' => $points->where('on', true)->count(),
|
||||
'offCount' => $points->where('on', false)->count(),
|
||||
]);
|
||||
}
|
||||
|
||||
/** Percentyl p (0-100) z tablicy liczb (interpolacja liniowa). */
|
||||
private function percentile(array $values, float $p): float
|
||||
{
|
||||
if (empty($values)) {
|
||||
return 0.0;
|
||||
}
|
||||
sort($values);
|
||||
$i = ($p / 100) * (count($values) - 1);
|
||||
$lo = (int) floor($i);
|
||||
$hi = (int) ceil($i);
|
||||
|
||||
return $lo === $hi ? (float) $values[$lo] : $values[$lo] + ($values[$hi] - $values[$lo]) * ($i - $lo);
|
||||
}
|
||||
|
||||
/** Ostatni stan ON/OFF (507) dla zbioru DRS_ID - jednym zapytaniem. */
|
||||
private function onOff(array $ids): \Illuminate\Support\Collection
|
||||
{
|
||||
if (empty($ids)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return 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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Concerns\SortsLists;
|
||||
use App\Http\Requests\StoreEngineRequest;
|
||||
use App\Http\Requests\UpdateEngineRequest;
|
||||
use App\Models\Engine;
|
||||
use App\Models\Manufacturer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Katalog silników - pełny CRUD (legacy site204 / site204A / site204E / site204inf).
|
||||
* Usuwanie logiczne (ENG_STATUS='0'). Newest methods: Eloquent + Form Requests.
|
||||
*/
|
||||
class EngineController extends Controller
|
||||
{
|
||||
use SortsLists;
|
||||
|
||||
private const MCF_ALL = 100;
|
||||
|
||||
/** Sortowalne kolumny listy: klucz nagłówka => kolumna SQL. */
|
||||
private const SORTABLE = [
|
||||
'name' => 'ENG_NAME',
|
||||
'voltage' => 'ENG_VOLTAGE',
|
||||
'power' => 'ENG_POWER',
|
||||
'mcf' => 'm.MCF_NAME',
|
||||
];
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$by = $request->query('by', 'name'); // 'name' | 'power'
|
||||
$q = trim((string) $request->query('q', ''));
|
||||
$mcf = (int) $request->query('mcf', self::MCF_ALL);
|
||||
|
||||
[$sort, $dir] = $this->sortParams($request);
|
||||
|
||||
$engines = Engine::query()
|
||||
->visible()
|
||||
->with('manufacturer')
|
||||
->when($q !== '' && $by === 'name', fn ($w) => $w->where('ENG_NAME', 'like', '%'.$q.'%'))
|
||||
->when($q !== '' && $by === 'power', function ($w) use ($q) {
|
||||
$v = (float) str_replace(',', '.', $q);
|
||||
$w->whereBetween('ENG_POWER', [$v - $v * 0.1, $v + $v * 0.1]);
|
||||
})
|
||||
->when($mcf !== self::MCF_ALL, fn ($w) => $w->where('ENG_MCF_ID', $mcf))
|
||||
// Sortowanie po producencie wymaga joinu (relacja nie jest w FROM).
|
||||
->when($sort === 'mcf', fn ($w) => $w
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'seg_engines.ENG_MCF_ID')
|
||||
->select('seg_engines.*'))
|
||||
->when($sort !== null, fn ($w) => $w->orderBy(self::SORTABLE[$sort], $dir))
|
||||
->orderBy('ENG_NAME')
|
||||
->paginate(20)
|
||||
->withQueryString();
|
||||
|
||||
return view('catalog.engines.index', [
|
||||
'engines' => $engines,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'by' => $by,
|
||||
'q' => $q,
|
||||
'mcf' => $mcf,
|
||||
'mcfAll' => self::MCF_ALL,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('catalog.engines.create', ['manufacturers' => $this->manufacturers()]);
|
||||
}
|
||||
|
||||
public function store(StoreEngineRequest $request)
|
||||
{
|
||||
$engine = Engine::create($this->toColumns($request->validated()) + ['ENG_STATUS' => '1']);
|
||||
|
||||
return redirect()->route('engines.show', $engine)
|
||||
->with('status', 'Dodano silnik „'.$engine->ENG_NAME.'".');
|
||||
}
|
||||
|
||||
public function show(Engine $engine)
|
||||
{
|
||||
$engine->load('manufacturer');
|
||||
|
||||
$relatedPumps = DB::connection('softspm')->table('seg_pump_engine_rel as r')
|
||||
->leftJoin('seg_pumps as p', 'p.PUP_ID', '=', 'r.RPE_PUP_ID')
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'p.PUP_MCF_ID')
|
||||
->where('r.RPE_ENG_ID', $engine->ENG_ID)
|
||||
->select('p.PUP_NAME', DB::raw('p.PUP_Q_N as q_n'), 'p.PUP_H_P', 'm.MCF_NAME')
|
||||
->get();
|
||||
|
||||
return view('catalog.engines.show', ['engine' => $engine, 'relatedPumps' => $relatedPumps]);
|
||||
}
|
||||
|
||||
public function edit(Engine $engine)
|
||||
{
|
||||
return view('catalog.engines.edit', [
|
||||
'engine' => $engine,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateEngineRequest $request, Engine $engine)
|
||||
{
|
||||
$engine->update($this->toColumns($request->validated()));
|
||||
|
||||
return redirect()->route('engines.show', $engine)
|
||||
->with('status', 'Zaktualizowano silnik „'.$engine->ENG_NAME.'".');
|
||||
}
|
||||
|
||||
public function destroy(Engine $engine)
|
||||
{
|
||||
$engine->update(['ENG_STATUS' => '0']); // usuwanie logiczne
|
||||
|
||||
return redirect()->route('engines.index')
|
||||
->with('status', 'Usunięto silnik „'.$engine->ENG_NAME.'".');
|
||||
}
|
||||
|
||||
/** Mapowanie pól formularza → kolumny seg_engines. */
|
||||
private function toColumns(array $data): array
|
||||
{
|
||||
return [
|
||||
'ENG_NAME' => $data['name'],
|
||||
'ENG_MCF_ID' => $data['manufacturer_id'],
|
||||
'ENG_POWER' => $data['power'],
|
||||
'ENG_VOLTAGE' => $data['voltage'],
|
||||
'ENG_ELECTRICITY' => $data['current'] ?? null,
|
||||
'ENG_ETA' => $data['eta'] ?? null,
|
||||
'ENG_COS_FI' => $data['cos_fi'] ?? null,
|
||||
'ENG_RPM' => $data['rpm'] ?? null,
|
||||
'ENG_LENGTH' => $data['length'] ?? null,
|
||||
'ENG_WEIGHT' => $data['weight'] ?? null,
|
||||
'ENG_DIAMETER' => $data['diameter'] ?? null,
|
||||
'ENG_WIRES' => $data['wires'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
private function manufacturers()
|
||||
{
|
||||
return Manufacturer::orderBy('MCF_NAME')->pluck('MCF_NAME', 'MCF_ID');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EquipmentFile;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Strumieniowanie załączników wyposażenia (BLOB z seg_equipments_files) -
|
||||
* wspólne dla wszystkich katalogów na seg_equipments (kable/orurowanie/
|
||||
* opomiarowanie). Podgląd/pobranie pliku po EQF_ID.
|
||||
*/
|
||||
class EquipmentFileController extends Controller
|
||||
{
|
||||
public function file(EquipmentFile $file): Response
|
||||
{
|
||||
return response($file->EQF_FILE ?? '', 200, [
|
||||
'Content-Type' => $file->EQF_FILE_TYPE ?: 'application/octet-stream',
|
||||
'Content-Disposition' => 'inline; filename="'.($file->EQF_FILE_NAME ?: 'plik').'"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Concerns\SortsLists;
|
||||
use App\Http\Requests\StoreMeteringRequest;
|
||||
use App\Http\Requests\UpdateMeteringRequest;
|
||||
use App\Models\Equipment;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Services\EquipmentFileService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Katalog opomiarowania - pełny CRUD (legacy site208 / A / E / inf).
|
||||
* seg_equipments (EQU_TYPE_ID=14). Pola jak w kablach (nazwa/producent/opis/
|
||||
* pliki); przy dodawaniu kategoria EQU_PARAM_02 ustawiana automatycznie na
|
||||
* słownik WELL_CATEGORY/SPM_FULL (jak legacy normalny formularz site208A).
|
||||
* Usuwanie logiczne; blokada gdy urządzenie jest użyte jako zasób
|
||||
* (seg_dev_resources DRS_DEV_TYPE=14) - inaczej niż kable (tam stan magazynowy).
|
||||
*
|
||||
* Wariant admina (site208Admin: ręczny wybór kategorii + język) NIE portowany -
|
||||
* dotyczył wyłącznie usera 'admin'; obecny model nie ma kontroli admin per-ekran.
|
||||
*/
|
||||
class MeteringController extends Controller
|
||||
{
|
||||
use SortsLists;
|
||||
|
||||
/** EQU_TYPE_ID opomiarowania (sys_dictionaries). */
|
||||
private const TYPE_METERING = 14;
|
||||
|
||||
private const MCF_ALL = 100;
|
||||
|
||||
/** Sortowalne kolumny listy. */
|
||||
private const SORTABLE = [
|
||||
'name' => 'EQU_NAME',
|
||||
'mcf' => 'm.MCF_NAME',
|
||||
];
|
||||
|
||||
public function __construct(private readonly EquipmentFileService $files) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$q = trim((string) $request->query('q', ''));
|
||||
$mcf = (int) $request->query('mcf', self::MCF_ALL);
|
||||
|
||||
[$sort, $dir] = $this->sortParams($request);
|
||||
|
||||
$items = Equipment::query()
|
||||
->visible()->ofType(self::TYPE_METERING)->with('manufacturer')
|
||||
->when($q !== '', fn ($w) => $w->where('EQU_NAME', 'like', '%'.$q.'%'))
|
||||
->when($mcf !== self::MCF_ALL, fn ($w) => $w->where('EQU_MCF_ID', $mcf))
|
||||
->when($sort === 'mcf', fn ($w) => $w
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'seg_equipments.EQU_MCF_ID')
|
||||
->select('seg_equipments.*'))
|
||||
->when($sort !== null, fn ($w) => $w->orderBy(self::SORTABLE[$sort], $dir))
|
||||
->orderBy('EQU_NAME')
|
||||
->paginate(20)
|
||||
->withQueryString();
|
||||
|
||||
return view('catalog.metering.index', [
|
||||
'items' => $items,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'q' => $q,
|
||||
'mcf' => $mcf,
|
||||
'mcfAll' => self::MCF_ALL,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('catalog.metering.create', ['manufacturers' => $this->manufacturers()]);
|
||||
}
|
||||
|
||||
public function store(StoreMeteringRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
|
||||
$item = Equipment::create([
|
||||
'EQU_NAME' => $data['name'],
|
||||
'EQU_MCF_ID' => $data['manufacturer_id'],
|
||||
'EQU_DESCRIPTION' => $data['description'] ?? null,
|
||||
'EQU_PARAM_02' => $this->defaultCategoryId(), // kategoria SPM_FULL (jak legacy)
|
||||
'EQU_TYPE_ID' => self::TYPE_METERING,
|
||||
'EQU_STATUS' => '1',
|
||||
]);
|
||||
|
||||
$this->saveFiles($request, $item->EQU_ID);
|
||||
|
||||
return redirect()->route('metering.show', $item)
|
||||
->with('status', 'Dodano urządzenie „'.$item->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
public function show(Equipment $metering)
|
||||
{
|
||||
abort_unless((int) $metering->EQU_TYPE_ID === self::TYPE_METERING, 404);
|
||||
$metering->load('manufacturer', 'files');
|
||||
|
||||
return view('catalog.metering.show', [
|
||||
'item' => $metering,
|
||||
'document' => $metering->files->first(fn ($f) => ! $f->isImage()),
|
||||
'image' => $metering->files->first(fn ($f) => $f->isImage()),
|
||||
'inUse' => $this->inUse($metering->EQU_ID),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Equipment $metering)
|
||||
{
|
||||
abort_unless((int) $metering->EQU_TYPE_ID === self::TYPE_METERING, 404);
|
||||
$metering->load('manufacturer', 'files');
|
||||
|
||||
return view('catalog.metering.edit', [
|
||||
'item' => $metering,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'document' => $metering->files->first(fn ($f) => ! $f->isImage()),
|
||||
'image' => $metering->files->first(fn ($f) => $f->isImage()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateMeteringRequest $request, Equipment $metering)
|
||||
{
|
||||
abort_unless((int) $metering->EQU_TYPE_ID === self::TYPE_METERING, 404);
|
||||
$data = $request->validated();
|
||||
|
||||
// Legacy update aktualizuje tylko nazwę/producenta/opis (kategoria zostaje).
|
||||
$metering->update([
|
||||
'EQU_NAME' => $data['name'],
|
||||
'EQU_MCF_ID' => $data['manufacturer_id'],
|
||||
'EQU_DESCRIPTION' => $data['description'] ?? null,
|
||||
]);
|
||||
|
||||
$this->saveFiles($request, $metering->EQU_ID);
|
||||
|
||||
return redirect()->route('metering.show', $metering)
|
||||
->with('status', 'Zaktualizowano urządzenie „'.$metering->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
public function destroy(Equipment $metering)
|
||||
{
|
||||
abort_unless((int) $metering->EQU_TYPE_ID === self::TYPE_METERING, 404);
|
||||
|
||||
// Legacy: blokuj, gdy urządzenie jest użyte jako zasób (seg_dev_resources, typ 14).
|
||||
if ($this->inUse($metering->EQU_ID)) {
|
||||
return redirect()->route('metering.show', $metering)
|
||||
->with('error', 'Nie można usunąć - urządzenie jest użyte w stanie opomiarowania.');
|
||||
}
|
||||
|
||||
$metering->update(['EQU_STATUS' => '0']); // usuwanie logiczne
|
||||
|
||||
return redirect()->route('metering.index')
|
||||
->with('status', 'Usunięto urządzenie „'.$metering->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
private function saveFiles(Request $request, int $equId): void
|
||||
{
|
||||
$user = $request->user()->USR_USERNAME;
|
||||
if ($request->hasFile('catalogue_file')) {
|
||||
$this->files->store($equId, $request->file('catalogue_file'), $user);
|
||||
}
|
||||
if ($request->hasFile('image_file')) {
|
||||
$this->files->store($equId, $request->file('image_file'), $user);
|
||||
}
|
||||
}
|
||||
|
||||
/** Czy urządzenie jest użyte jako zasób opomiarowania (seg_dev_resources, typ 14). */
|
||||
private function inUse(int $equId): bool
|
||||
{
|
||||
return DB::connection('softspm')->table('seg_dev_resources')
|
||||
->where('DRS_DEV_TYPE', self::TYPE_METERING)
|
||||
->where('DRS_DEV_ID', $equId)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/** DIC_ID słownikowej kategorii WELL_CATEGORY/SPM_FULL (jak hidden w site208A). */
|
||||
private function defaultCategoryId(): ?int
|
||||
{
|
||||
return DB::connection('softspm')->table('sys_dictionaries')
|
||||
->where('DIC_TYPE', 'WELL_CATEGORY')
|
||||
->where('DIC_NAME', 'SPM_FULL')
|
||||
->value('DIC_ID');
|
||||
}
|
||||
|
||||
private function manufacturers()
|
||||
{
|
||||
return Manufacturer::orderBy('MCF_NAME')->pluck('MCF_NAME', 'MCF_ID');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Concerns\SortsLists;
|
||||
use App\Http\Requests\StorePipingRequest;
|
||||
use App\Http\Requests\UpdatePipingRequest;
|
||||
use App\Models\Equipment;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Services\EquipmentFileService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Katalog orurowania i armatury - pełny CRUD (legacy site207 / A / E / inf).
|
||||
* seg_equipments (EQU_TYPE_ID=15) + dwa parametry: EQU_PARAM_01=średnica [mm],
|
||||
* EQU_PARAM_02=długość [m] / ciśnienie [bar]. Załączniki BLOB jak w kablach.
|
||||
* Usuwanie logiczne; blokada gdy na stanie (seg_equipments_stock). Pliki przez
|
||||
* wspólny endpoint equipment.file.
|
||||
*/
|
||||
class PipingController extends Controller
|
||||
{
|
||||
use SortsLists;
|
||||
|
||||
/** EQU_TYPE_ID orurowania/armatury (sys_dictionaries). */
|
||||
private const TYPE_PIPING = 15;
|
||||
|
||||
private const MCF_ALL = 100;
|
||||
|
||||
/** Sortowalne kolumny listy. */
|
||||
private const SORTABLE = [
|
||||
'name' => 'EQU_NAME',
|
||||
'diameter' => 'EQU_PARAM_01',
|
||||
'length' => 'EQU_PARAM_02',
|
||||
'mcf' => 'm.MCF_NAME',
|
||||
];
|
||||
|
||||
public function __construct(private readonly EquipmentFileService $files) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$q = trim((string) $request->query('q', ''));
|
||||
$mcf = (int) $request->query('mcf', self::MCF_ALL);
|
||||
|
||||
[$sort, $dir] = $this->sortParams($request);
|
||||
|
||||
$items = Equipment::query()
|
||||
->visible()->ofType(self::TYPE_PIPING)->with('manufacturer')
|
||||
->when($q !== '', fn ($w) => $w->where('EQU_NAME', 'like', '%'.$q.'%'))
|
||||
->when($mcf !== self::MCF_ALL, fn ($w) => $w->where('EQU_MCF_ID', $mcf))
|
||||
->when($sort === 'mcf', fn ($w) => $w
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'seg_equipments.EQU_MCF_ID')
|
||||
->select('seg_equipments.*'))
|
||||
->when($sort !== null, fn ($w) => $w->orderBy(self::SORTABLE[$sort], $dir))
|
||||
->orderBy('EQU_NAME')->orderBy('EQU_PARAM_01')
|
||||
->paginate(20)
|
||||
->withQueryString();
|
||||
|
||||
return view('catalog.piping.index', [
|
||||
'items' => $items,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'q' => $q,
|
||||
'mcf' => $mcf,
|
||||
'mcfAll' => self::MCF_ALL,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('catalog.piping.create', ['manufacturers' => $this->manufacturers()]);
|
||||
}
|
||||
|
||||
public function store(StorePipingRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
|
||||
$item = Equipment::create([
|
||||
'EQU_NAME' => $data['name'],
|
||||
'EQU_MCF_ID' => $data['manufacturer_id'],
|
||||
'EQU_PARAM_01' => $data['diameter'],
|
||||
'EQU_PARAM_02' => $data['length'] ?? null,
|
||||
'EQU_DESCRIPTION' => $data['description'] ?? null,
|
||||
'EQU_TYPE_ID' => self::TYPE_PIPING,
|
||||
'EQU_STATUS' => '1',
|
||||
]);
|
||||
|
||||
$this->saveFiles($request, $item->EQU_ID);
|
||||
|
||||
return redirect()->route('piping.show', $item)
|
||||
->with('status', 'Dodano pozycję „'.$item->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
public function show(Equipment $piping)
|
||||
{
|
||||
abort_unless((int) $piping->EQU_TYPE_ID === self::TYPE_PIPING, 404);
|
||||
$piping->load('manufacturer', 'files');
|
||||
|
||||
return view('catalog.piping.show', [
|
||||
'item' => $piping,
|
||||
'document' => $piping->files->first(fn ($f) => ! $f->isImage()),
|
||||
'image' => $piping->files->first(fn ($f) => $f->isImage()),
|
||||
'inStock' => $this->inStock($piping->EQU_ID),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Equipment $piping)
|
||||
{
|
||||
abort_unless((int) $piping->EQU_TYPE_ID === self::TYPE_PIPING, 404);
|
||||
$piping->load('manufacturer', 'files');
|
||||
|
||||
return view('catalog.piping.edit', [
|
||||
'item' => $piping,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'document' => $piping->files->first(fn ($f) => ! $f->isImage()),
|
||||
'image' => $piping->files->first(fn ($f) => $f->isImage()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdatePipingRequest $request, Equipment $piping)
|
||||
{
|
||||
abort_unless((int) $piping->EQU_TYPE_ID === self::TYPE_PIPING, 404);
|
||||
$data = $request->validated();
|
||||
|
||||
$piping->update([
|
||||
'EQU_NAME' => $data['name'],
|
||||
'EQU_MCF_ID' => $data['manufacturer_id'],
|
||||
'EQU_PARAM_01' => $data['diameter'],
|
||||
'EQU_PARAM_02' => $data['length'] ?? null,
|
||||
'EQU_DESCRIPTION' => $data['description'] ?? null,
|
||||
]);
|
||||
|
||||
$this->saveFiles($request, $piping->EQU_ID);
|
||||
|
||||
return redirect()->route('piping.show', $piping)
|
||||
->with('status', 'Zaktualizowano pozycję „'.$piping->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
public function destroy(Equipment $piping)
|
||||
{
|
||||
abort_unless((int) $piping->EQU_TYPE_ID === self::TYPE_PIPING, 404);
|
||||
|
||||
if ($this->inStock($piping->EQU_ID)) {
|
||||
return redirect()->route('piping.show', $piping)
|
||||
->with('error', 'Nie można usunąć - pozycja jest powiązana ze stanem magazynowym.');
|
||||
}
|
||||
|
||||
$piping->update(['EQU_STATUS' => '0']); // usuwanie logiczne
|
||||
|
||||
return redirect()->route('piping.index')
|
||||
->with('status', 'Usunięto pozycję „'.$piping->EQU_NAME.'".');
|
||||
}
|
||||
|
||||
private function saveFiles(Request $request, int $equId): void
|
||||
{
|
||||
$user = $request->user()->USR_USERNAME;
|
||||
if ($request->hasFile('catalogue_file')) {
|
||||
$this->files->store($equId, $request->file('catalogue_file'), $user);
|
||||
}
|
||||
if ($request->hasFile('image_file')) {
|
||||
$this->files->store($equId, $request->file('image_file'), $user);
|
||||
}
|
||||
}
|
||||
|
||||
private function inStock(int $equId): bool
|
||||
{
|
||||
return DB::connection('softspm')->table('seg_equipments_stock')
|
||||
->where('SES_EQU_ID', $equId)->exists();
|
||||
}
|
||||
|
||||
private function manufacturers()
|
||||
{
|
||||
return Manufacturer::orderBy('MCF_NAME')->pluck('MCF_NAME', 'MCF_ID');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Concerns\SortsLists;
|
||||
use App\Http\Requests\StorePumpRequest;
|
||||
use App\Http\Requests\UpdatePumpRequest;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Models\Pump;
|
||||
use App\Services\PumpService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Katalog pomp - pełny CRUD (legacy site203 / site203A / site203E / site203inf).
|
||||
* Newest methods: Eloquent, resource controller, Form Requests, serwis domenowy.
|
||||
*/
|
||||
class PumpController extends Controller
|
||||
{
|
||||
use SortsLists;
|
||||
|
||||
/** Producent „Wszyscy" (jak legacy: 100). */
|
||||
private const MCF_ALL = 100;
|
||||
|
||||
/** Sortowalne kolumny listy: klucz nagłówka => kolumna SQL. */
|
||||
private const SORTABLE = [
|
||||
'name' => 'PUP_NAME',
|
||||
'flow' => 'PUP_Q_N',
|
||||
'hp' => 'PUP_H_P',
|
||||
'mcf' => 'm.MCF_NAME',
|
||||
];
|
||||
|
||||
public function __construct(private readonly PumpService $pumps) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$by = $request->query('by', 'name'); // 'name' | 'flow'
|
||||
$q = trim((string) $request->query('q', ''));
|
||||
$mcf = (int) $request->query('mcf', self::MCF_ALL);
|
||||
|
||||
[$sort, $dir] = $this->sortParams($request);
|
||||
|
||||
$pumps = Pump::query()
|
||||
->visible()
|
||||
->with('manufacturer')
|
||||
->where('PUP_Q_N', '>', 0)
|
||||
->when($q !== '' && $by === 'name', fn ($w) => $w->where('PUP_NAME', 'like', '%'.$q.'%'))
|
||||
->when($q !== '' && $by === 'flow', function ($w) use ($q) {
|
||||
$v = (float) str_replace(',', '.', $q);
|
||||
$w->whereBetween('PUP_Q_N', [$v - $v * 0.1, $v + $v * 0.1]); // skala UAT bez ×60
|
||||
})
|
||||
->when($mcf !== self::MCF_ALL, fn ($w) => $w->where('PUP_MCF_ID', $mcf))
|
||||
// Sortowanie po producencie wymaga joinu (relacja nie jest w FROM).
|
||||
->when($sort === 'mcf', fn ($w) => $w
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'seg_pumps.PUP_MCF_ID')
|
||||
->select('seg_pumps.*'))
|
||||
->when($sort !== null, fn ($w) => $w->orderBy(self::SORTABLE[$sort], $dir))
|
||||
->orderBy('PUP_Q_N')
|
||||
->orderBy('PUP_NAME')
|
||||
->paginate(20)
|
||||
->withQueryString();
|
||||
|
||||
return view('catalog.pumps.index', [
|
||||
'pumps' => $pumps,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'by' => $by,
|
||||
'q' => $q,
|
||||
'mcf' => $mcf,
|
||||
'mcfAll' => self::MCF_ALL,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('catalog.pumps.create', ['manufacturers' => $this->manufacturers()]);
|
||||
}
|
||||
|
||||
public function store(StorePumpRequest $request)
|
||||
{
|
||||
$pump = $this->pumps->create($request->validated());
|
||||
|
||||
return redirect()
|
||||
->route('pumps.show', $pump)
|
||||
->with('status', 'Dodano pompę „'.$pump->PUP_NAME.'".');
|
||||
}
|
||||
|
||||
public function show(Request $request, Pump $pump)
|
||||
{
|
||||
$pump->load('manufacturer');
|
||||
|
||||
$h = $this->pumps->curve($pump, 'H'); // x = m³/h (bez /60)
|
||||
$eta = $this->pumps->curve($pump, 'ETA');
|
||||
|
||||
// Warunki rysowania jak legacy: H bez zer, η dopuszcza 1 zero (punkt Q=0).
|
||||
$hDrawable = $this->pumps->curveDrawable($h, 0);
|
||||
$etaDrawable = $this->pumps->curveDrawable($eta, 1);
|
||||
|
||||
// Przelicz dla zadanego Q [m³/h]. Domyślnie Q_n; clamp do zakresu.
|
||||
$maxX = (float) ($h->max('x') ?? 0);
|
||||
$qn = round((float) $pump->PUP_Q_N, 3);
|
||||
$qIn = $request->query('q');
|
||||
$q = $qIn === null ? $qn : (float) str_replace(',', '.', (string) $qIn);
|
||||
$outOfRange = $q < 0 || $q > $maxX;
|
||||
$q = max(0, min($q, $maxX));
|
||||
|
||||
$relatedEngines = DB::connection('softspm')->table('seg_pump_engine_rel as r')
|
||||
->leftJoin('seg_engines as e', 'e.ENG_ID', '=', 'r.RPE_ENG_ID')
|
||||
->leftJoin('seg_manufacturers as m', 'm.MCF_ID', '=', 'e.ENG_MCF_ID')
|
||||
->where('r.RPE_PUP_ID', $pump->PUP_ID)
|
||||
->select('e.ENG_NAME', 'e.ENG_VOLTAGE', 'e.ENG_POWER', 'm.MCF_NAME')
|
||||
->get();
|
||||
|
||||
return view('catalog.pumps.show', [
|
||||
'pump' => $pump,
|
||||
'h' => $h,
|
||||
'eta' => $eta,
|
||||
'hDrawable' => $hDrawable,
|
||||
'etaDrawable' => $etaDrawable,
|
||||
'hPlot' => $hDrawable ? $this->pumps->plot($h) : null,
|
||||
'etaPlot' => $etaDrawable ? $this->pumps->plot($eta) : null,
|
||||
'relatedEngines' => $relatedEngines,
|
||||
'q' => $q,
|
||||
'hAtQ' => $hDrawable ? $this->pumps->valueAt($h, $q) : null,
|
||||
'etaAtQ' => $etaDrawable ? $this->pumps->valueAt($eta, $q) : null,
|
||||
'outOfRange' => $outOfRange,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Pump $pump)
|
||||
{
|
||||
$pump->load('manufacturer');
|
||||
|
||||
return view('catalog.pumps.edit', [
|
||||
'pump' => $pump,
|
||||
'manufacturers' => $this->manufacturers(),
|
||||
'h' => $this->pumps->curve($pump, 'H'),
|
||||
'eta' => $this->pumps->curve($pump, 'ETA'),
|
||||
'qn' => round((float) $pump->PUP_Q_N, 3),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdatePumpRequest $request, Pump $pump)
|
||||
{
|
||||
$this->pumps->update($pump, $request->validated());
|
||||
|
||||
return redirect()
|
||||
->route('pumps.show', $pump)
|
||||
->with('status', 'Zaktualizowano pompę „'.$pump->PUP_NAME.'".');
|
||||
}
|
||||
|
||||
public function destroy(Pump $pump)
|
||||
{
|
||||
$this->pumps->delete($pump);
|
||||
|
||||
return redirect()
|
||||
->route('pumps.index')
|
||||
->with('status', 'Usunięto pompę „'.$pump->PUP_NAME.'".');
|
||||
}
|
||||
|
||||
/** MCF_ID => MCF_NAME do selectów. */
|
||||
private function manufacturers()
|
||||
{
|
||||
return Manufacturer::orderBy('MCF_NAME')->pluck('MCF_NAME', 'MCF_ID');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Concerns\SortsLists;
|
||||
use App\Services\WellService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* Studnie - migracja sites/site401.php (część read-only: lista + wyszukiwanie
|
||||
* + paginacja). Database-first na bazie `softspm`.
|
||||
*
|
||||
* Wydajność: legacy robił N+1 (osobne podzapytania o pompę, silnik i stan
|
||||
* ON/OFF dla KAŻDEGO wiersza). Tu wszystko w JEDNYM zapytaniu z JOIN-ami
|
||||
* (+ podzapytanie po ostatni ON/OFF). Parytet wyników do weryfikacji na
|
||||
* serwerze testowym - patrz README_SOFTSPM.md.
|
||||
*/
|
||||
class WellController extends Controller
|
||||
{
|
||||
use SortsLists;
|
||||
|
||||
/** Status "Wszystkie" - wartość spoza słownika (jak legacy: 100). */
|
||||
private const STATUS_ALL = 100;
|
||||
|
||||
/** Sortowalne kolumny listy: klucz nagłówka => kolumna SQL (z aliasów joinów). */
|
||||
private const SORTABLE = [
|
||||
'name' => '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 index(Request $request)
|
||||
{
|
||||
$department = $request->session()->get('department');
|
||||
$search = trim((string) $request->query('q', ''));
|
||||
// Domyślny status jak w legacy: 3.
|
||||
$status = (int) $request->query('status', 3);
|
||||
|
||||
[$sort, $dir] = $this->sortParams($request);
|
||||
|
||||
// Słownik statusów do filtra (legacy: DIC_ID in (3,4) + "Wszystkie").
|
||||
$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();
|
||||
|
||||
// Stan ON/OFF dociągany tylko dla 20 widocznych studni (wydajność).
|
||||
$this->attachOnOff($wells);
|
||||
|
||||
return view('wells.index', [
|
||||
'wells' => $wells,
|
||||
'search' => $search,
|
||||
'status' => $status,
|
||||
'statusOptions' => $statusOptions,
|
||||
'statusAll' => self::STATUS_ALL,
|
||||
'department' => $department,
|
||||
]);
|
||||
}
|
||||
|
||||
/** Formularz dodania studni (migracja site301A). */
|
||||
public function create(WellService $wells)
|
||||
{
|
||||
return view('wells.create', $this->formLookups($wells));
|
||||
}
|
||||
|
||||
/** Zapis nowej studni (migracja site301inf, dodawanie). */
|
||||
public function store(Request $request, WellService $wells)
|
||||
{
|
||||
$data = $this->validateWell($request, $wells, isUpdate: false);
|
||||
|
||||
$drsId = $wells->create(
|
||||
$data,
|
||||
$request->session()->get('department'),
|
||||
$request->user()->USR_USERNAME,
|
||||
);
|
||||
|
||||
return redirect()
|
||||
->route('wells.index')
|
||||
->with('status', 'Dodano studnię „'.$data['name'].'".');
|
||||
}
|
||||
|
||||
/** Formularz edycji studni (migracja site301E). */
|
||||
public function edit(int $drs, WellService $wells)
|
||||
{
|
||||
$well = $wells->findForEdit($drs);
|
||||
abort_if(! $well, 404);
|
||||
|
||||
return view('wells.edit', $this->formLookups($wells) + ['well' => $well]);
|
||||
}
|
||||
|
||||
/** Aktualizacja studni (migracja site301inf, subWellUpdate). */
|
||||
public function update(int $drs, Request $request, WellService $wells)
|
||||
{
|
||||
$data = $this->validateWell($request, $wells, isUpdate: true);
|
||||
|
||||
$wells->update($drs, $data, $request->user()->USR_USERNAME);
|
||||
|
||||
return redirect()
|
||||
->route('wells.index')
|
||||
->with('status', 'Zaktualizowano studnię „'.$data['name'].'".');
|
||||
}
|
||||
|
||||
/** Wspólne słowniki do formularzy dodania/edycji. */
|
||||
private function formLookups(WellService $wells): array
|
||||
{
|
||||
return [
|
||||
'waterIntakes' => $wells->waterIntakes(),
|
||||
'categories' => $wells->dictionaryById('WELL_CATEGORY'),
|
||||
'statuses' => $wells->dictionaryById('WELL_STATUS_LEVEL_01'),
|
||||
'sysTypes' => $wells->dictionaryDescriptions('WELL_SYSTEM_TYPE'),
|
||||
'statusDescs' => $wells->dictionaryDescriptions('WELL_STATUS_LEVEL_03'),
|
||||
];
|
||||
}
|
||||
|
||||
/** Walidacja + normalizacja pól formularza studni. */
|
||||
private function validateWell(Request $request, WellService $wells, bool $isUpdate): array
|
||||
{
|
||||
$rules = [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'water_intake_id' => ['required', 'integer', 'min:1'],
|
||||
'category_id' => ['required', 'integer'],
|
||||
'status_id' => ['required', 'integer'],
|
||||
'latitude' => ['nullable', 'string', 'max:128'],
|
||||
'longitude' => ['nullable', 'string', 'max:128'],
|
||||
];
|
||||
|
||||
if ($isUpdate) {
|
||||
$rules += [
|
||||
'well_id' => ['required', 'integer'],
|
||||
'pp_scope' => ['nullable', 'string', 'max:16'],
|
||||
'hh_scope' => ['nullable', 'string', 'max:16'],
|
||||
'sys_type' => ['nullable', 'string', 'max:128'],
|
||||
'status_desc' => ['nullable', 'string', 'max:255'],
|
||||
'available_date' => ['nullable', 'date'],
|
||||
'build_time' => ['nullable', 'string', 'max:8'],
|
||||
'wle_id' => ['nullable', 'integer'],
|
||||
];
|
||||
} else {
|
||||
$rules += [
|
||||
'diameter' => ['nullable', 'string', 'max:32'],
|
||||
'depth' => ['nullable', 'string', 'max:32'],
|
||||
'supply' => ['nullable', 'string', 'max:32'],
|
||||
'available_date' => ['nullable', 'date'],
|
||||
];
|
||||
}
|
||||
|
||||
$data = $request->validate($rules);
|
||||
|
||||
// Unikalność nazwy tylko przy dodawaniu (legacy sprawdzał przed INSERT).
|
||||
if (! $isUpdate && $wells->nameExists($data['name'])) {
|
||||
throw ValidationException::withMessages(['name' => 'Studnia o tej nazwie już istnieje.']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jedno zapytanie zastępujące listę + N+1 z legacy.
|
||||
*
|
||||
* Mapowanie tabel legacy:
|
||||
* seg_wells (w) - studnia
|
||||
* seg_dev_resources (drw,12) - zasób studni: DRS_ID, dział, status
|
||||
* seg_well_pump_eng_rel (rel) - powiązanie studnia↔pompa/silnik
|
||||
* seg_dev_resources (drp,10) - zasób pompy (nr seryjny)
|
||||
* seg_pumps (p) - pompa (nazwa)
|
||||
* seg_dev_resources (dre,11) - zasób silnika (nr seryjny)
|
||||
* seg_engines (e) - silnik (nazwa)
|
||||
* seg_dev_resources_det (507) - ostatni stan ON/OFF
|
||||
*/
|
||||
private function buildQuery(?string $department, string $search, int $status)
|
||||
{
|
||||
// UWAGA WYDAJNOŚĆ: NIE łączymy tu ostatniego ON/OFF z seg_dev_resources_det
|
||||
// (tabela ma miliardy wierszy) - agregacja po całości była wąskim gardłem
|
||||
// paginacji. Stan ON/OFF dociągamy osobno tylko dla bieżącej strony
|
||||
// w attachOnOff().
|
||||
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')
|
||||
// Pompa
|
||||
->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')
|
||||
// Silnik
|
||||
->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',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dociąga ostatni stan ON/OFF (DRD_RESOURCES_DETAIL=507) TYLKO dla studni
|
||||
* z bieżącej strony - po ich DRS_ID (indeks INX_DRD_DRS_DETAIL_DATE),
|
||||
* zamiast agregować całą tabelę seg_dev_resources_det. Każdy wiersz
|
||||
* dostaje właściwość ->onoff.
|
||||
*/
|
||||
private function attachOnOff(\Illuminate\Contracts\Pagination\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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Concerns\SortsLists;
|
||||
use App\Services\PumpService;
|
||||
use App\Services\SyndisService;
|
||||
use App\Services\WellAssessmentService;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Eksploatacja studni (legacy grupa site401). Osobny moduł pod sekcją
|
||||
* Eksploatacja - NIE myli się z modułem Studnie (wells.*, sekcja Stan).
|
||||
*
|
||||
* Etap 1: lista eksploatacyjna (site401.php) + szkielet karty studni
|
||||
* (site401mark). Dashboard ocen pracy układu, energochłonność, wykresy
|
||||
* Hp/η, parametry pracy (site401inf) i podstrony (lift/mhis/offhis/demand)
|
||||
* dokładamy w kolejnych etapach - opierają się na danych SYNDIS
|
||||
* (rep_his_data_everyday, rep_soap_syndis_quality_data) i próbach pomp.
|
||||
*/
|
||||
class WellOperationController extends Controller
|
||||
{
|
||||
use SortsLists;
|
||||
|
||||
/** Status "Wszystkie" - wartość spoza słownika (jak legacy: 100). */
|
||||
private const STATUS_ALL = 100;
|
||||
|
||||
/** Sortowalne kolumny listy (aliasy z joinów). */
|
||||
private const SORTABLE = [
|
||||
'name' => '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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Ustawia język aplikacji na podstawie cookie 'locale' (PL/EN/DE) - wybór
|
||||
* z menu użytkownika. Tłumaczenia stringów to osobny krok (i18n legacy
|
||||
* language/*.php -> lang/); tu wyłącznie ustawienie locale + atrybut lang.
|
||||
*/
|
||||
class SetLocale
|
||||
{
|
||||
private const SUPPORTED = ['pl', 'en', 'de'];
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$locale = $request->cookie('locale');
|
||||
if (is_string($locale) && in_array($locale, self::SUPPORTED, true)) {
|
||||
app()->setLocale($locale);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Walidacja dodania/edycji typu kabla (site206A/E). Pliki opcjonalne:
|
||||
* dokumentacja katalogowa (PDF/Office) oraz zdjęcie. Limit 8 MB (mediumblob 16 MB).
|
||||
*/
|
||||
class StoreCableRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'description' => ['nullable', 'string', 'max:255'],
|
||||
'catalogue_file' => ['nullable', 'file', 'mimes:pdf,doc,docx,xls,xlsx,ppt,pptx', 'max:8192'],
|
||||
'image_file' => ['nullable', 'image', 'mimes:jpg,jpeg,png,gif,bmp', 'max:8192'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'typ kabla',
|
||||
'manufacturer_id' => 'producent',
|
||||
'description' => 'opis',
|
||||
'catalogue_file' => 'dokumentacja katalogowa',
|
||||
'image_file' => 'zdjęcie',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreEngineRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
foreach (['power', 'voltage', 'current', 'eta', 'cos_fi', 'rpm', 'length', 'weight', 'diameter'] as $f) {
|
||||
$this->merge([$f => $this->dec($this->$f)]);
|
||||
}
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
// Unikalność: nazwa + napięcie (jak legacy).
|
||||
'name' => ['required', 'string', 'max:128', Rule::unique('seg_engines', 'ENG_NAME')->where(fn ($q) => $q->where('ENG_VOLTAGE', $this->voltage))],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'power' => ['required', 'numeric'],
|
||||
'voltage' => ['required', 'numeric'],
|
||||
'current' => ['nullable', 'numeric'],
|
||||
'eta' => ['nullable', 'numeric'],
|
||||
'cos_fi' => ['nullable', 'numeric'],
|
||||
'rpm' => ['nullable', 'numeric'],
|
||||
'length' => ['nullable', 'numeric'],
|
||||
'weight' => ['nullable', 'numeric'],
|
||||
'diameter' => ['nullable', 'numeric'],
|
||||
'wires' => ['nullable', 'string', 'max:32'],
|
||||
];
|
||||
}
|
||||
|
||||
private function dec(mixed $v): ?string
|
||||
{
|
||||
return $v === null || $v === '' ? null : str_replace(',', '.', (string) $v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Walidacja dodania/edycji urządzenia opomiarowania (site208A/E). Pola jak
|
||||
* w kablach (nazwa, producent, opis, pliki). Kategoria (EQU_PARAM_02=SPM_FULL)
|
||||
* ustawiana automatycznie w serwisie/kontrolerze - jak legacy normalny dodawania.
|
||||
*/
|
||||
class StoreMeteringRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'description' => ['nullable', 'string', 'max:255'],
|
||||
'catalogue_file' => ['nullable', 'file', 'mimes:pdf,doc,docx,xls,xlsx,ppt,pptx', 'max:8192'],
|
||||
'image_file' => ['nullable', 'image', 'mimes:jpg,jpeg,png,gif,bmp', 'max:8192'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'nazwa',
|
||||
'manufacturer_id' => 'producent',
|
||||
'description' => 'opis',
|
||||
'catalogue_file' => 'dokumentacja katalogowa',
|
||||
'image_file' => 'zdjęcie',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Walidacja dodania/edycji orurowania i armatury (site207A/E). Średnica
|
||||
* wymagana (legacy float_req), długość/ciśnienie opcjonalne. Pliki jak w kablach.
|
||||
*/
|
||||
class StorePipingRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'diameter' => ['required', 'string', 'max:128'],
|
||||
'length' => ['nullable', 'string', 'max:128'],
|
||||
'description' => ['nullable', 'string', 'max:255'],
|
||||
'catalogue_file' => ['nullable', 'file', 'mimes:pdf,doc,docx,xls,xlsx,ppt,pptx', 'max:8192'],
|
||||
'image_file' => ['nullable', 'image', 'mimes:jpg,jpeg,png,gif,bmp', 'max:8192'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'nazwa',
|
||||
'manufacturer_id' => 'producent',
|
||||
'diameter' => 'średnica',
|
||||
'length' => 'długość / ciśnienie',
|
||||
'description' => 'opis',
|
||||
'catalogue_file' => 'dokumentacja katalogowa',
|
||||
'image_file' => 'zdjęcie',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorePumpRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'q_n' => $this->dec($this->q_n),
|
||||
'length' => $this->dec($this->length),
|
||||
'diameter' => $this->dec($this->diameter),
|
||||
'weight' => $this->dec($this->weight),
|
||||
'h' => collect($this->h ?? [])->map(fn ($v) => $this->dec($v))->all(),
|
||||
'eta' => collect($this->eta ?? [])->map(fn ($v) => $this->dec($v))->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:128', 'unique:seg_pumps,PUP_NAME'],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'q_n' => ['required', 'numeric', 'gt:0'],
|
||||
// 8 punktów krzywych - H wymagane (>0), η wymagane (0 dozwolone przy Q=0).
|
||||
'h' => ['required', 'array', 'size:8'],
|
||||
'h.*' => ['required', 'numeric'],
|
||||
'eta' => ['required', 'array', 'size:8'],
|
||||
'eta.*' => ['required', 'numeric'],
|
||||
'length' => ['nullable', 'numeric'],
|
||||
'diameter' => ['nullable', 'numeric'],
|
||||
'weight' => ['nullable', 'numeric'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'h.*.required' => 'Podaj wartość H dla każdego punktu Q.',
|
||||
'eta.*.required' => 'Podaj wartość η dla każdego punktu Q.',
|
||||
];
|
||||
}
|
||||
|
||||
private function dec(mixed $v): ?string
|
||||
{
|
||||
return $v === null || $v === '' ? null : str_replace(',', '.', (string) $v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Walidacja edycji typu kabla (site206E). Reguły jak przy dodawaniu - wgranie
|
||||
* nowego pliku zastępuje dotychczasowy tej samej roli; brak pliku = bez zmian.
|
||||
*/
|
||||
class UpdateCableRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'description' => ['nullable', 'string', 'max:255'],
|
||||
'catalogue_file' => ['nullable', 'file', 'mimes:pdf,doc,docx,xls,xlsx,ppt,pptx', 'max:8192'],
|
||||
'image_file' => ['nullable', 'image', 'mimes:jpg,jpeg,png,gif,bmp', 'max:8192'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'typ kabla',
|
||||
'manufacturer_id' => 'producent',
|
||||
'description' => 'opis',
|
||||
'catalogue_file' => 'dokumentacja katalogowa',
|
||||
'image_file' => 'zdjęcie',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateEngineRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
foreach (['power', 'voltage', 'current', 'eta', 'cos_fi', 'rpm', 'length', 'weight', 'diameter'] as $f) {
|
||||
$this->merge([$f => $this->dec($this->$f)]);
|
||||
}
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:128', Rule::unique('seg_engines', 'ENG_NAME')->where(fn ($q) => $q->where('ENG_VOLTAGE', $this->voltage))->ignore($this->route('engine'))],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'power' => ['required', 'numeric'],
|
||||
'voltage' => ['required', 'numeric'],
|
||||
'current' => ['nullable', 'numeric'],
|
||||
'eta' => ['nullable', 'numeric'],
|
||||
'cos_fi' => ['nullable', 'numeric'],
|
||||
'rpm' => ['nullable', 'numeric'],
|
||||
'length' => ['nullable', 'numeric'],
|
||||
'weight' => ['nullable', 'numeric'],
|
||||
'diameter' => ['nullable', 'numeric'],
|
||||
'wires' => ['nullable', 'string', 'max:32'],
|
||||
];
|
||||
}
|
||||
|
||||
private function dec(mixed $v): ?string
|
||||
{
|
||||
return $v === null || $v === '' ? null : str_replace(',', '.', (string) $v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Walidacja edycji urządzenia opomiarowania (site208E). Reguły jak przy
|
||||
* dodawaniu; wgranie nowego pliku zastępuje dotychczasowy tej samej roli.
|
||||
*/
|
||||
class UpdateMeteringRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'description' => ['nullable', 'string', 'max:255'],
|
||||
'catalogue_file' => ['nullable', 'file', 'mimes:pdf,doc,docx,xls,xlsx,ppt,pptx', 'max:8192'],
|
||||
'image_file' => ['nullable', 'image', 'mimes:jpg,jpeg,png,gif,bmp', 'max:8192'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'nazwa',
|
||||
'manufacturer_id' => 'producent',
|
||||
'description' => 'opis',
|
||||
'catalogue_file' => 'dokumentacja katalogowa',
|
||||
'image_file' => 'zdjęcie',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Walidacja edycji orurowania i armatury (site207E). Reguły jak przy dodawaniu;
|
||||
* wgranie nowego pliku zastępuje dotychczasowy tej samej roli.
|
||||
*/
|
||||
class UpdatePipingRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'diameter' => ['required', 'string', 'max:128'],
|
||||
'length' => ['nullable', 'string', 'max:128'],
|
||||
'description' => ['nullable', 'string', 'max:255'],
|
||||
'catalogue_file' => ['nullable', 'file', 'mimes:pdf,doc,docx,xls,xlsx,ppt,pptx', 'max:8192'],
|
||||
'image_file' => ['nullable', 'image', 'mimes:jpg,jpeg,png,gif,bmp', 'max:8192'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'nazwa',
|
||||
'manufacturer_id' => 'producent',
|
||||
'diameter' => 'średnica',
|
||||
'length' => 'długość / ciśnienie',
|
||||
'description' => 'opis',
|
||||
'catalogue_file' => 'dokumentacja katalogowa',
|
||||
'image_file' => 'zdjęcie',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdatePumpRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'length' => $this->dec($this->length),
|
||||
'diameter' => $this->dec($this->diameter),
|
||||
'weight' => $this->dec($this->weight),
|
||||
'h' => collect($this->h ?? [])->map(fn ($v) => $this->dec($v))->all(),
|
||||
'eta' => collect($this->eta ?? [])->map(fn ($v) => $this->dec($v))->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:128', Rule::unique('seg_pumps', 'PUP_NAME')->ignore($this->route('pump'))],
|
||||
'manufacturer_id' => ['required', 'integer', 'exists:seg_manufacturers,MCF_ID'],
|
||||
'length' => ['nullable', 'numeric'],
|
||||
'diameter' => ['nullable', 'numeric'],
|
||||
'weight' => ['nullable', 'numeric'],
|
||||
'h' => ['nullable', 'array'],
|
||||
'h.*' => ['nullable', 'numeric'],
|
||||
'eta' => ['nullable', 'array'],
|
||||
'eta.*' => ['nullable', 'numeric'],
|
||||
];
|
||||
}
|
||||
|
||||
private function dec(mixed $v): ?string
|
||||
{
|
||||
return $v === null || $v === '' ? null : str_replace(',', '.', (string) $v);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user