This commit is contained in:
Dwi Swandhana
2026-05-21 06:37:14 +07:00
parent acac1fe3c8
commit 3e9e8daea6
6 changed files with 116 additions and 121 deletions
@@ -115,18 +115,18 @@ class BiorepositoryController extends Controller
public function storeSpecimen(Request $request)
{
$validator = Validator::make($request->all(), [
'rack_id' => 'required|exists:bio_racks,id',
'kategorisimpan' => 'required|in:A,B,C,D',
'shelfnomor' => 'required|integer|min:1',
'raknomor' => 'required|integer|min:1',
'slotnomor' => 'required|integer|min:1',
'boxnomor' => 'required|integer|min:1',
'tubenomor' => 'required|integer|min:1',
'nmbakteri' => 'nullable|max:200',
'strain' => 'nullable|in:Gram Negatif,Gram Positif',
'stored_at' => 'required|date',
'volume' => 'nullable|numeric|min:0',
'volume_ambil' => 'nullable|numeric|min:0',
'rack_id' => 'required|exists:bio_racks,id',
'kategorisimpan' => 'required|in:A,B,C,D',
'shelfnomor' => 'required|integer|min:1',
'raknomor' => 'required|integer|min:1',
'slotnomor' => 'required|integer|min:1',
'boxnomor' => 'required|integer|min:1',
'tubenomor' => 'required|integer|min:1',
'nmbakteri' => 'nullable|max:200',
'strain' => 'nullable|in:Gram Negatif,Gram Positif',
'stored_at' => 'required|date',
'volume' => 'nullable|numeric|min:0',
'volume_ambil' => 'nullable|numeric|min:0',
'existing_specimen_id' => 'nullable|integer',
]);
@@ -206,6 +206,17 @@ class BiorepositoryController extends Controller
$counter++;
$generatedCode = $baseCode.'-'.$counter;
}
$sampleid = $request->input('sampleid');
if ($sampleid) {
$cekdata = Periksa::where('nofoto', $sampleid)->first();
if (!$cekdata) {
$sampleid = $cekdata->id;
} else {
$sampleid = null;
}
} else {
$sampleid = null;
}
BioSpecimen::create([
'cabinet_id' => $rack->cabinet_id,
@@ -220,8 +231,9 @@ class BiorepositoryController extends Controller
'tube_number' => $request->input('tubenomor'),
'bacteria_name' => $request->input('nmbakteri'),
'strain' => $request->input('strain'),
'atcc' => $atccByUser,
'atcc' => $request->input('atcc') ?? $atccByUser,
'input_by' => Session::get('nama'),
'sampleid' => $sampleid,
'stored_at' => $request->input('stored_at'),
'storage_condition' => $this->mapStorageCondition($request->input('kategorisimpan')),
'volume' => (string) $request->input('volume'),
@@ -421,35 +421,19 @@ class ReportController extends Controller
->where('db_komponenjawaban.isidata', '!=', '');
});
}
/**
* CURSOR → Streaming data, tidak boros memori
*/
$periksaCursor = $query->orderBy($dateColumn, 'ASC')->cursor();
$hasil = [];
/**
* Karena cursor tidak bisa pluck di awal,
* kita kumpulkan accnumber & poli_id sambil streaming
*/
$acc = [];
$poli_ids = [];
$periksaCursor = $query->orderBy($dateColumn, 'ASC')->cursor();
$hasil = [];
$acc = [];
$poli_ids = [];
foreach ($periksaCursor as $r) {
$acc[] = $r->nofoto;
$poli_ids[] = $r->poli_id;
$hasil[] = $r; // simpan pointer row
$hasil[] = $r;
}
/** Jika tidak ada data */
if (empty($hasil)) {
return response()->json([]);
}
/**
* Ambil komponen terkait (1 query saja)
*/
$komponen = DB::table('db_komponenjawaban')
->select('accnumber','komponen','isidata')
->whereIn('accnumber', array_unique($acc))
@@ -459,27 +443,17 @@ class ReportController extends Controller
])
->get()
->groupBy('accnumber');
/**
* Ambil poli terkait (1 query)
*/
$poli = DB::table('poli')
->select('id','subpoli','modaliti2')
->whereIn('id', array_unique($poli_ids))
->get()
->keyBy('id');
/**
* Proses setiap row → tidak ada map() untuk hemat memori
*/
foreach ($hasil as $row) {
$k = $komponen[$row->nofoto] ?? collect([]);
$row->id_bakteri01 = optional($k->firstWhere('komponen','id_bakteri01'))->isidata;
$row->id_bakteri02 = optional($k->firstWhere('komponen','id_bakteri02'))->isidata;
$row->bakteri = optional($k->firstWhere('komponen','bakteri'))->isidata;
$row->hasil_mdr = $k->whereIn('komponen', ['bakterisir', 'id_bakterisir01', 'id_bakterisir02'])
$k = $komponen[$row->nofoto] ?? collect([]);
$row->id_bakteri01 = optional($k->firstWhere('komponen','id_bakteri01'))->isidata;
$row->id_bakteri02 = optional($k->firstWhere('komponen','id_bakteri02'))->isidata;
$row->bakteri = optional($k->firstWhere('komponen','bakteri'))->isidata;
$row->hasil_mdr = $k->whereIn('komponen', ['bakterisir', 'id_bakterisir01', 'id_bakterisir02'])
->pluck('isidata')
->filter(function ($value) {
return trim(strip_tags((string) $value)) !== '';
@@ -487,15 +461,12 @@ class ReportController extends Controller
->unique()
->implode('; ');
$row->filefoto = "<a href='".url('/hasil/'.$row->nofoto)."'>$row->nofoto</a>";
$p = $poli[$row->poli_id] ?? null;
$row->filefoto = "<a href='".url('/hasil/'.$row->nofoto)."'>$row->nofoto</a>";
$p = $poli[$row->poli_id] ?? null;
$row->subpoli = $p->subpoli ?? null;
$row->modaliti2 = $this->resolveTatTargetDays($p->modaliti2 ?? '1');
// Hitung status target
if (!empty($row->verifikasi)) {
$selisih = $this->getTatElapsedDays($row->mulai, $row->verifikasi);
$selisih = $this->getTatElapsedDays($row->mulai, $row->verifikasi);
$row->selisih_hari = $selisih;
$row->status_target = $this->resolveTatStatus($row->mulai, $row->verifikasi, $row->modaliti2);
} else {
@@ -503,10 +474,6 @@ class ReportController extends Controller
$row->status_target = "TIDAK ADA HASIL";
}
}
/**
* Return untuk dua mode (rekaptat / data penuh)
*/
if ($jenisreport == 'rekaptat') {
$rekap = collect($hasil)->groupBy('kd_spesimen')->map(function($g){
return [
@@ -517,18 +484,14 @@ class ReportController extends Controller
'total' => $g->count(),
];
})->values();
return response()->json($rekap);
}
return response()->json($hasil);
}
}
public function genRekapAntibiotik(Request $request) {
$bulan = $request->input('bulan');
$tahun = $request->input('tahun');
if ($tahun == '' OR is_null($tahun)){
$getarray = explode('?', $bulan);
$bulan = $getarray[0] ?? date('m');
@@ -543,19 +506,14 @@ class ReportController extends Controller
if ($bulan != 'ALL' && $bulan != 'Pick Month') {
$query->whereMonth('daftar', $bulan);
}
// 3. Pagination (50 Data)
$orderbydate = $query->paginate(50);
$orderbydate = $query->paginate(50);
$orderbydate->appends(['bulan' => $bulan, 'tahun' => $tahun]);
// 4. Mapping Data Antibiotik (Hanya untuk 50 pasien ini)
$pageIds = $orderbydate->pluck('id')->toArray();
$antibiotikLookup = $this->mapAntibiotikData($pageIds);
$pageIds = $orderbydate->pluck('id')->toArray();
$antibiotikLookup = $this->mapAntibiotikData($pageIds);
return view('admin.rekapantibiotik', [
'orderbydate' => $orderbydate,
'antibiotikLookup' => $antibiotikLookup,
'listAntibiotik' => $listAntibiotik, // Kirim header dinamis ke View
'listAntibiotik' => $listAntibiotik,
'bulan' => $bulan,
'tahun' => $tahun
]);
@@ -998,36 +956,25 @@ class ReportController extends Controller
$bulan = str_replace('bulan=', '', $parts[0]);
$tahun = str_replace('tahun=', '', $parts[1] ?? date('Y'));
}
// 1. Dapatkan Header Dinamis
$listAntibiotik = $this->getDynamicAntibioticHeaders($bulan, $tahun);
$response = new StreamedResponse(function() use ($bulan, $tahun, $listAntibiotik) {
$handle = fopen('php://output', 'w');
// Header Statis
$staticHeaders = [
$handle = fopen('php://output', 'w');
$staticHeaders = [
'No', 'Status', 'No.RM', 'Name', 'Order', 'Gender', 'Date',
'Urgensi', 'Comming From', 'Code', 'Spesimen', 'Finish',
'BHP Media', 'BHP Pot Sputum', 'BHP Pot Urine', 'BHP Oshe',
'BHP Obyek Glass', 'BHP Botol BD', 'BHP Parafilm', 'BHP Tips',
'BHP Cotton Swab', 'BHP Ab Tambahan', 'ESBL', 'MRSA'
];
// Gabung Header Statis + Header Dinamis dari DB
];
fputcsv($handle, array_merge($staticHeaders, $listAntibiotik));
// Query Utama (Chunking)
$query = Periksa::query()->whereYear('daftar', $tahun);
if ($bulan != 'ALL' && $bulan != 'Pick Month') {
$query->whereMonth('daftar', $bulan);
}
$query->chunk(500, function($rows) use ($handle, $listAntibiotik) {
// Ambil data nilai antibiotik untuk chunk ini
$chunkIds = $rows->pluck('id')->toArray();
$antibiotikLookup = $this->mapAntibiotikData($chunkIds);
$chunkIds = $rows->pluck('id')->toArray();
$antibiotikLookup = $this->mapAntibiotikData($chunkIds);
foreach ($rows as $row) {
$csvRow = [
$row->noloket,
@@ -1055,14 +1002,10 @@ class ReportController extends Controller
$row->id_esbl,
$row->id_mrsa
];
// Loop sesuai Header Dinamis
foreach ($listAntibiotik as $headerAb) {
// Cek apakah pasien ini punya nilai utk antibiotik tsb
$val = $antibiotikLookup[$row->id][$headerAb] ?? '';
$csvRow[] = $val;
$val = $antibiotikLookup[$row->id][$headerAb] ?? '';
$csvRow[] = $val;
}
fputcsv($handle, $csvRow);
}
});
@@ -1132,13 +1075,8 @@ class ReportController extends Controller
$bulan = str_replace('bulan=', '', $bulan);
$tahun = str_replace('tahun=', '', $tahun);
}
// komponen Ziehl yang mau dianalisa
$komponenList = $this->getZnReportKomponenList();
// Nilai Ziehl yang ingin dihitung
$nilaiList = Organisms::where('kelompok', 'Pewarnaan Ziehl Nielsen')->pluck('name')->toArray();
// 1. Ambil periksa sesuai bulan & tahun
$komponenList = $this->getZnReportKomponenList();
$nilaiList = Organisms::where('kelompok', 'Pewarnaan Ziehl Nielsen')->pluck('name')->toArray();
if ($bulan == '' OR $bulan == 'ALL'){
$periksa = Periksa::with(['komponen' => function($q) use ($komponenList){
$q->whereIn('komponen', $komponenList);
@@ -1153,21 +1091,15 @@ class ReportController extends Controller
->whereYear('mulai', $tahun)
->get();
}
// 2. Ambil distinct kd_spesimen → ini akan jadi kolom
$kdSpesimenList = $periksa
->pluck('kd_spesimen')
->unique()
->values();
// 3. Bangun struktur perhitungan
$rekapLaki = [];
$rekapPerempuan = [];
foreach ($nilaiList as $nilai) {
foreach ($kdSpesimenList as $spesimen) {
// hitung laki-laki
$rekapLaki[$nilai][$spesimen] = $periksa
->where('kd_spesimen', $spesimen)
->where('jkpasien', 'L')
@@ -1175,7 +1107,6 @@ class ReportController extends Controller
return $this->periksaHasZnReportValue($px, $nilai, $komponenList);
})->count();
// hitung perempuan
$rekapPerempuan[$nilai][$spesimen] = $periksa
->where('kd_spesimen', $spesimen)
->where('jkpasien', 'P')
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('bio_specimens', function (Blueprint $table) {
if (!Schema::hasColumn('bio_specimens', 'atcc')) {
$table->string('atcc', 150)->nullable()->after('strain');
}
if (!Schema::hasColumn('bio_specimens', 'sampleid')) {
$table->string('sampleid', 150)->nullable()->after('atcc');
}
});
}
public function down(): void
{
Schema::table('bio_specimens', function (Blueprint $table) {
if (Schema::hasColumn('bio_specimens', 'atcc')) {
$table->dropColumn('atcc');
}
if (Schema::hasColumn('bio_specimens', 'sampleid')) {
$table->dropColumn('sampleid');
}
});
}
};
@@ -271,7 +271,9 @@
<th>Lemari</th>
<th>Nama Rack</th>
<th>Tgl Simpan</th>
<th>ATCC</th>
<th>Input By</th>
<th>View</th>
<th>Aksi</th>
</tr>
</thead>
@@ -289,7 +291,9 @@
<th>Lemari</th>
<th>Nama Rack</th>
<th>Tgl Simpan</th>
<th>ATCC</th>
<th>Input By</th>
<th>View</th>
<th></th>
</tr>
</tfoot>
@@ -308,7 +312,15 @@
<td>{{ $row->cabinet->code ?? '-' }}</td>
<td>{{ $row->rack->name ?? '-' }}</td>
<td>{{ $row->stored_at }}</td>
<td>{{ $row->atcc }}</td>
<td>{{ $row->input_by }}</td>
<td>
@if ($row->sampleid)
<a href="{{ url('/mikro/'.$row->sampleid.'/expertise?return_to=biorepository') }}" class="btn btn-sm btn-info">View</a>
@else
-
@endif
</td>
<td class="text-center">
<form method="POST" action="{{ route('biorepository.deleteSpecimen', $row->id) }}" class="d-inline js-delete-specimen">
@csrf
@@ -463,14 +475,12 @@
<input type="date" class="form-control" id="stored_at" name="stored_at" value="{{ date('Y-m-d') }}" required>
</div>
</div>
<div class="form-group m-b-25">
<div class="col-12">
<label>Bactery Name</label>
<input type="text" class="form-control" id="nmbakteri" name="nmbakteri" required>
</div>
</div>
<div class="form-group m-b-25">
<div class="col-12">
<label>Strain</label>
@@ -480,7 +490,18 @@
</select>
</div>
</div>
<div class="form-group m-b-25">
<div class="col-12">
<label>ATCC</label>
<input type="text" class="form-control" id="atcc" name="atcc" value="{{Session('nama')}}">
</div>
</div>
<div class="form-group m-b-25">
<div class="col-12">
<label>Sample ID</label>
<input type="text" class="form-control" id="sampleid" name="sampleid" placeholder="Contoh: 26-000001 (isi jika sudah memiliki sample ID dari MyLIS)" >
</div>
</div>
<div class="form-group m-b-25" id="group-volume-awal">
<div class="col-12">
<label>Volume Awal (ml)</label>
@@ -5504,20 +5504,19 @@
}
},
{ text: 'Acc.No', datafield: 'tlsnofoto', width: 100, align: 'center', cellsalign: 'center'},
{ text: 'Asal Sample', datafield: 'modality', width: 100, align: 'center', cellsalign: 'center'},
{ text: 'Kelompok', filtertype: 'checkedlist', datafield: 'kd_spesimen', width: 75, cellsalign: 'left', align: 'center'},
{ text: 'Specimen', filtertype: 'checkedlist', datafield: 'nm_spesimen', width: 230, cellsalign: 'left', align: 'center'},
{ text: 'No.RM', datafield: 'tlsnoregister', width: 100, cellsalign: 'left', align: 'center'},
{ text: 'Name', datafield: 'tlsnama', width: 150, cellsalign: 'left', align: 'center'},
{ text: 'Date', datafield: 'daftartgl', width: 80, cellsalign: 'center', align: 'center'},
{ text: 'Time', datafield: 'daftarjam', width: 80, cellsalign: 'center', align: 'center'},
{ text: 'Inputor', filtertype: 'checkedlist', datafield: 'nmpendaftar', width: 110, cellsalign: 'left', align: 'center'},
{ text: 'Comming From', filtertype: 'checkedlist', datafield: 'asalpasien', width: 120, cellsalign: 'left', align: 'center'},
{ text: 'Service', filtertype: 'checkedlist', datafield: 'tlsreques', width: 150, cellsalign: 'left', align: 'center'},
{ text: 'Status', filtertype: 'checkedlist', datafield: 'tlsstatus', width: 150, cellsalign: 'left', align: 'center'},
{ text: 'Themeplate', filtertype: 'checkedlist', datafield: 'dlp', width: 75, cellsalign: 'left', align: 'center'},
{ text: 'Kelompok', filtertype: 'checkedlist', datafield: 'kd_spesimen', width: 75, cellsalign: 'left', align: 'center'},
{ text: 'Specimen', filtertype: 'checkedlist', datafield: 'nm_spesimen', width: 230, cellsalign: 'left', align: 'center'},
{ text: 'Updated_at', datafield: 'updated_at', filtertype: 'date', width: 150, cellsalign: 'left', align: 'center'},
{ text: 'Duration', cellsrenderer: tlsdurasi, width: 100, cellsalign: 'left', align: 'center' },
{ text: 'Inputor', filtertype: 'checkedlist', datafield: 'nmpendaftar', width: 110, cellsalign: 'left', align: 'center'},
{ text: 'Themeplate', filtertype: 'checkedlist', datafield: 'dlp', width: 75, cellsalign: 'left', align: 'center'},
]
});
$("html, body").animate({ scrollTop: 0 }, "slow");
+4 -5
View File
@@ -5539,20 +5539,19 @@
}
},
{ text: 'Acc.No', datafield: 'tlsnofoto', width: 100, align: 'center', cellsalign: 'center'},
{ text: 'Asal Sample', datafield: 'modality', width: 100, align: 'center', cellsalign: 'center'},
{ text: 'Kelompok', filtertype: 'checkedlist', datafield: 'kd_spesimen', width: 75, cellsalign: 'left', align: 'center'},
{ text: 'Specimen', filtertype: 'checkedlist', datafield: 'nm_spesimen', width: 230, cellsalign: 'left', align: 'center'},
{ text: 'No.RM', datafield: 'tlsnoregister', width: 100, cellsalign: 'left', align: 'center'},
{ text: 'Name', datafield: 'tlsnama', width: 150, cellsalign: 'left', align: 'center'},
{ text: 'Date', datafield: 'daftartgl', width: 80, cellsalign: 'center', align: 'center'},
{ text: 'Time', datafield: 'daftarjam', width: 80, cellsalign: 'center', align: 'center'},
{ text: 'Inputor', filtertype: 'checkedlist', datafield: 'nmpendaftar', width: 110, cellsalign: 'left', align: 'center'},
{ text: 'Comming From', filtertype: 'checkedlist', datafield: 'asalpasien', width: 120, cellsalign: 'left', align: 'center'},
{ text: 'Service', filtertype: 'checkedlist', datafield: 'tlsreques', width: 150, cellsalign: 'left', align: 'center'},
{ text: 'Status', filtertype: 'checkedlist', datafield: 'tlsstatus', width: 150, cellsalign: 'left', align: 'center'},
{ text: 'Themeplate', filtertype: 'checkedlist', datafield: 'dlp', width: 75, cellsalign: 'left', align: 'center'},
{ text: 'Kelompok', filtertype: 'checkedlist', datafield: 'kd_spesimen', width: 75, cellsalign: 'left', align: 'center'},
{ text: 'Specimen', filtertype: 'checkedlist', datafield: 'nm_spesimen', width: 230, cellsalign: 'left', align: 'center'},
{ text: 'Updated_at', datafield: 'updated_at', filtertype: 'date', width: 150, cellsalign: 'left', align: 'center'},
{ text: 'Duration', cellsrenderer: tlsdurasi, width: 100, cellsalign: 'left', align: 'center' },
{ text: 'Inputor', filtertype: 'checkedlist', datafield: 'nmpendaftar', width: 110, cellsalign: 'left', align: 'center'},
{ text: 'Themeplate', filtertype: 'checkedlist', datafield: 'dlp', width: 75, cellsalign: 'left', align: 'center'},
]
});
$("html, body").animate({ scrollTop: 0 }, "slow");