Compare commits

...
100 Commits
Author SHA1 Message Date
Dwi Swandhana 97de187bc8 update 2026-08-19 14:27:05 +07:00
Dwi Swandhana 5b284fd0d3 update 2026-08-18 05:16:08 +07:00
Dwi Swandhana 6c6520b3eb update 2026-08-13 10:27:05 +07:00
Dwi Swandhana 59d448360f update 2026-08-13 10:25:13 +07:00
Dwi Swandhana 3decbec88e update 2026-08-12 10:51:17 +07:00
Dwi Swandhana c3af859439 update 2026-08-10 10:45:31 +07:00
Dwi Swandhana ec2c3178ee update 2026-08-10 09:43:15 +07:00
Dwi Swandhana fd3068bead update 2026-08-09 13:25:49 +07:00
Dwi Swandhana 550fc64763 update 2026-08-09 13:16:47 +07:00
Dwi Swandhana 58e27eb92b update 2026-08-09 12:56:27 +07:00
Dwi Swandhana 765bfd91f3 update 2026-08-09 12:52:13 +07:00
Dwi Swandhana 36582a9811 update 2026-08-09 12:50:25 +07:00
Dwi Swandhana d764663f35 update 2026-08-09 12:49:08 +07:00
Dwi Swandhana 14301f7216 update 2026-08-09 12:44:29 +07:00
Dwi Swandhana 9dc9bccc76 update 2026-08-09 12:43:08 +07:00
Dwi Swandhana 36ad5d7af2 update 2026-08-09 12:40:12 +07:00
Dwi Swandhana a04c5330d5 update 2026-08-09 12:36:44 +07:00
Dwi Swandhana 3ef25c9e95 update 2026-08-09 12:23:07 +07:00
Dwi Swandhana b82a799e20 update 2026-08-06 19:32:16 +07:00
Dwi Swandhana f0dc7987a9 update 2026-08-06 17:59:06 +07:00
Dwi Swandhana 73a9ac103e update 2026-08-06 17:57:59 +07:00
Dwi Swandhana 3e6b70d7bd update 2026-08-05 15:45:34 +07:00
Dwi Swandhana 136470d4ba update 2026-08-05 15:37:52 +07:00
Dwi Swandhana 9363533c3c update 2026-08-04 15:23:34 +07:00
Dwi Swandhana b93784be6a update 2026-08-04 15:04:13 +07:00
Dwi Swandhana caa039b5b4 update 2026-08-04 11:34:17 +07:00
Dwi Swandhana d8fc98d1a8 update 2026-08-04 11:15:59 +07:00
Dwi Swandhana fd865f6756 update 2026-08-04 09:49:25 +07:00
Dwi Swandhana 587267b63a update 2026-08-04 06:24:43 +07:00
Dwi Swandhana 08a62487ab update 2026-08-04 06:22:45 +07:00
Dwi Swandhana bb2c893872 update 2026-08-04 06:11:52 +07:00
Dwi Swandhana 3192425fc6 update 2026-08-04 06:05:18 +07:00
Dwi Swandhana c2cbdff4e5 update 2026-08-04 06:01:06 +07:00
Dwi Swandhana d369d3754b update 2026-08-04 05:55:29 +07:00
Dwi Swandhana 2bf4cac343 update 2026-08-04 05:34:07 +07:00
Dwi Swandhana 207c9ef17d update 2026-08-04 05:32:25 +07:00
Dwi Swandhana f97105aeb0 update 2026-08-04 04:58:13 +07:00
Dwi Swandhana 60681f08cc update 2026-08-03 18:34:45 +07:00
Dwi Swandhana b957be735d update 2026-08-02 20:21:35 +07:00
Dwi Swandhana 3e1ed72727 update 2026-08-02 10:46:34 +07:00
Dwi Swandhana 334728e16e update 2026-08-02 05:31:32 +07:00
Dwi Swandhana 16f541ca77 update 2026-08-02 05:23:11 +07:00
Dwi Swandhana cd07dee705 update 2026-08-01 20:59:08 +07:00
Dwi Swandhana c7771029ee update 2026-08-01 20:55:51 +07:00
Dwi Swandhana 19c3e0b6d4 update 2026-08-01 20:44:49 +07:00
Dwi Swandhana d5f6f2812c update 2026-08-01 20:42:19 +07:00
Dwi Swandhana 4ebcbf0869 update 2026-08-01 20:37:14 +07:00
Dwi Swandhana 8e87beeb9f update 2026-08-01 20:33:09 +07:00
Dwi Swandhana 2f60f36307 update 2026-08-01 17:05:17 +07:00
Dwi Swandhana 03855a99b1 update 2026-08-01 17:04:47 +07:00
Dwi Swandhana 0891978d46 update 2026-08-01 17:02:31 +07:00
Dwi Swandhana a3a98b13e3 update 2026-08-01 12:18:05 +07:00
Dwi Swandhana 119f73403e update 2026-08-01 12:15:25 +07:00
Dwi Swandhana 410916f963 update 2026-07-31 14:05:55 +07:00
Dwi Swandhana d1e6ab0133 update 2026-07-29 05:23:37 +07:00
Dwi Swandhana 73b2e1ca7c update 2026-07-29 05:08:33 +07:00
Dwi Swandhana 9cc78b23e0 update 2026-07-22 16:36:21 +07:00
Dwi Swandhana 213270ede1 update 2026-07-22 16:33:42 +07:00
Dwi Swandhana e04d43dfdf update 2026-07-22 14:06:01 +07:00
Dwi Swandhana 848f0e4100 update 2026-07-22 13:32:12 +07:00
Dwi Swandhana 5774d55f71 update 2026-07-22 10:01:55 +07:00
Dwi Swandhana 9e4774bd5e update 2026-07-22 09:07:43 +07:00
Dwi Swandhana 92dc35396b update 2026-07-22 05:47:03 +07:00
Dwi Swandhana 040284ec29 update 2026-07-22 05:38:14 +07:00
Dwi Swandhana b222a79283 update 2026-07-22 05:34:34 +07:00
Dwi Swandhana 32db765b98 update 2026-07-22 05:18:55 +07:00
Dwi Swandhana 6d0f9a8bf6 update 2026-07-21 19:08:27 +07:00
Dwi Swandhana d944647129 update 2026-07-21 05:45:47 +07:00
Dwi Swandhana 707e2cb841 update 2026-07-20 11:25:33 +07:00
Dwi Swandhana 0123036226 update 2026-07-20 05:58:11 +07:00
Dwi Swandhana 08984801f9 update 2026-07-19 21:54:01 +07:00
Dwi Swandhana 82dc63822e update 2026-07-19 21:41:33 +07:00
Dwi Swandhana d1e9fe03d6 update 2026-07-19 21:37:35 +07:00
Dwi Swandhana 8a233d6c1b update 2026-07-19 21:32:48 +07:00
Dwi Swandhana 335355a5ba update 2026-07-19 21:25:03 +07:00
Dwi Swandhana 4450076726 update 2026-07-19 21:23:09 +07:00
Dwi Swandhana 5e097c1abc update 2026-07-19 21:16:52 +07:00
Dwi Swandhana 92117e3320 update 2026-07-19 21:16:38 +07:00
Dwi Swandhana 3f0f6534d7 update 2026-07-19 21:09:16 +07:00
Dwi Swandhana 7febb03b5d update 2026-07-19 20:57:33 +07:00
Dwi Swandhana 30e39875c0 update 2026-07-19 20:47:08 +07:00
Dwi Swandhana 616f0658d6 update 2026-07-12 18:38:11 +07:00
Dwi Swandhana 9b831791f5 update 2026-07-11 11:26:25 +07:00
Dwi Swandhana 79ff65d87c update 2026-07-11 06:32:24 +07:00
Dwi Swandhana d6bba3e216 update 2026-07-11 06:21:16 +07:00
Dwi Swandhana 943282e3f9 update 2026-07-06 10:25:31 +07:00
Dwi Swandhana 0f93db0431 update 2026-07-06 10:08:26 +07:00
Dwi Swandhana f2dbbf9f5e update 2026-07-06 10:06:29 +07:00
Dwi Swandhana 741ac2dffd update 2026-07-06 09:25:59 +07:00
Dwi Swandhana 510e8a2352 update 2026-07-04 09:36:08 +07:00
Dwi Swandhana 146bab0a46 update 2026-07-04 05:17:15 +07:00
Dwi Swandhana d725332bc3 update 2026-07-04 05:13:44 +07:00
Dwi Swandhana 67ff560077 update 2026-07-03 21:04:10 +07:00
Dwi Swandhana 0f8c73bc50 update 2026-07-03 20:58:47 +07:00
Dwi Swandhana f857eec85c update 2026-07-03 20:48:38 +07:00
Dwi Swandhana ed92325536 update 2026-06-19 20:19:34 +07:00
Dwi Swandhana 9a65fa8227 update 2026-06-17 05:04:21 +07:00
Dwi Swandhana 480db23635 update 2026-06-15 06:01:37 +07:00
Dwi Swandhana 7758df12f2 update 2026-06-15 05:53:14 +07:00
Dwi Swandhana cc11aecb10 update 2026-06-15 05:49:25 +07:00
217 changed files with 29782 additions and 2808 deletions

No files matched your search

Vendored
BIN
View File
Binary file not shown.
+630
View File
@@ -0,0 +1,630 @@
# MyLIS-RSSA
### Microbiology Laboratory Information System
---
## Bahasa Indonesia
**MyLIS-RSSA** adalah ekosistem aplikasi Laboratory Information System untuk layanan mikrobiologi RSUD Dr. Saiful Anwar. Repo ini berisi aplikasi web/backend Laravel, aplikasi pendamping mobile/desktop Flutter, dan service listener Python untuk komunikasi alat laboratorium.
Sistem ini membantu alur kerja laboratorium mulai dari penerimaan order, registrasi sampel, pengelompokan pemeriksaan, input expertise, verifikasi hasil, pelaporan, notifikasi nilai kritis, sampai integrasi dengan SIMRS dan instrumen laboratorium.
---
### Aplikasi di Dalam Repo
#### 1. LIS Web & Backend (`htdocs`)
Aplikasi utama berbasis Laravel untuk operasional laboratorium. Modul ini menangani autentikasi, dashboard, pendaftaran pasien/sampel, daftar pemeriksaan, template hasil, expertise, laporan, label/barcode, logbook, biorepository, gudang BHP, API mobile, backup cloud, dan integrasi SIMRS.
Ringkasan fungsi:
- Registrasi order dari SIMRS melalui endpoint `registerpasien`, `registerulang`, dan `registerjson`.
- Pengelolaan pasien, dokter, poli/ruangan, user, template jawaban, dan subjawaban.
- Worklist pemeriksaan mikrobiologi berdasarkan buku/kategori sampel.
- Input dan cetak hasil pemeriksaan, adendum, label, form, dan laporan.
- Dashboard Early Warning System dan notifikasi nilai kritis.
- Modul logbook PPDS/SPV, biorepository, dan SIMBHP.
- API `/api/mobile` untuk aplikasi MyLIS Flutter.
- Utilitas backup/restore database ke cloud dan export/simulasi payload register API.
#### 2. MyLIS Flutter (`mylis`)
Aplikasi mobile/desktop pendamping untuk akses cepat ke LIS Laravel. Aplikasi ini dipakai untuk login, cek koneksi server, dashboard, pencarian sampel, scanner barcode, Early Warning System, penerimaan pekerjaan awal, daftar pemeriksaan, detail pemeriksaan, notifikasi nilai kritis, dan pengisian expertise native.
Screen expertise yang tersedia:
- `CCI`
- `Kultur`
- `Pewarna Langsung`
- `TBC`
- `Viral Load`
- `IgM IgG Leptospira`
- `PCR COVID`
#### 3. Listener Instrumen (`listener`)
Service Python untuk komunikasi dengan instrumen laboratorium, terutama jalur serial/TCP dan GeneXpert. Service ini membaca, memproses, dan mengirim pesan instrumen seperti ASTM/HL7, lalu menyimpan atau meneruskan data ke database LIS.
Ringkasan fungsi:
- Listener TCP untuk GeneXpert.
- Komunikasi serial untuk perangkat seperti Vitek dan BACTEC.
- Mapping kode pemeriksaan LIS ke host test code alat.
- Antrean order instrumen dan pemrosesan hasil.
- Logging harian dan log per thread untuk troubleshooting.
---
### Cara Kerja Aplikasi
Alur utama sistem:
```text
SIMRS / user web
-> Laravel LIS menerima order dan data pasien
-> Sampel masuk ke worklist mikrobiologi
-> Petugas menerima, memproses, dan mengisi hasil/expertise
-> Supervisor melakukan verifikasi atau finalisasi
-> Hasil dicetak, dikirim, atau diekstrak kembali ke SIMRS
```
Alur mobile:
```text
MyLIS Flutter
-> login ke Laravel melalui /api/mobile/login
-> mengambil dashboard, worklist, detail pemeriksaan, dan template expertise
-> menyimpan draft, preliminary, final, atau kirim ke SPV
-> Laravel menyimpan perubahan ke database LIS
```
Alur instrumen:
```text
Instrumen laboratorium
-> listener Python menerima ASTM/HL7/serial/TCP
-> data diparse dan dicocokkan dengan order/sampel
-> hasil atau status instrumen disimpan ke database
-> Laravel menampilkan hasil untuk proses validasi dan pelaporan
```
Alur backup cloud:
```text
Server production
-> pg_dump database
-> upload file .backup secara chunked ke cloud
-> restore dapat dilakukan dengan download backup terbaru dan pg_restore
```
Detail backup, restore, export payload `registerApi`, dan simulasi kirim data SIMRS tersedia di [htdocs/README.md](/Users/duidev/htdocs/lis/htdocs/README.md).
---
### Teknologi
- **Backend**: Laravel 10, PHP 8.1+ dengan platform Composer PHP 8.4.
- **API & Auth**: Laravel route API, token terenkripsi untuk mobile, Laravel Sanctum/Passport tersedia di dependency.
- **Frontend Web**: Blade, Bootstrap, jQuery, DataTables, Livewire 3, Vite.
- **Database**: konfigurasi `.env` mendukung MySQL; deployment/backup production juga memakai PostgreSQL untuk database `lismikro`.
- **Reporting & Dokumen**: TCPDF, FPDI, PhpSpreadsheet, QR/barcode generator.
- **Integrasi**: HL7, ASTM, serial communication, FTP, HTTP client Guzzle, bridging BPJS.
- **Mobile/Desktop**: Flutter, Dart, Material UI, HTTP client, shared preferences, URL launcher, mobile scanner.
- **Listener Engine**: Python 3, Flask, SQLAlchemy, psycopg2, pyserial, hl7.
- **Runtime Ops**: Artisan commands, cron backup, optional Docker Compose.
---
### Instalasi LIS Web & Backend (`htdocs`)
Prasyarat:
- PHP 8.1 atau lebih baru.
- Composer.
- Node.js dan npm.
- Database MySQL atau PostgreSQL sesuai konfigurasi deployment.
- Ekstensi PHP yang dibutuhkan Laravel dan package proyek.
Langkah instalasi:
```bash
cd htdocs
composer install
npm install
cp .env.example .env
php artisan key:generate
```
Sesuaikan konfigurasi utama di `.env`:
```env
APP_URL=https://lis.swandhana.test
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_lis
DB_USERNAME=root
DB_PASSWORD=
```
Jalankan migrasi bila database target memang disiapkan dari migration project:
```bash
php artisan migrate
```
Jalankan aplikasi lokal:
```bash
php artisan serve
npm run dev
```
Build aset production:
```bash
npm run build
```
Perintah verifikasi yang umum:
```bash
php artisan route:list
php artisan route:list --path=mobile
php -l app/Http/Controllers/MobileApiController.php
```
---
### Instalasi MyLIS Flutter (`mylis`)
Prasyarat:
- Flutter SDK sesuai `pubspec.yaml` (`Dart SDK ^3.12.2`).
- Android Studio/Xcode sesuai platform target.
- Server Laravel LIS yang bisa diakses dari device atau simulator.
Langkah instalasi:
```bash
cd mylis
flutter pub get
flutter run
```
Jalankan dengan URL Laravel tertentu:
```bash
flutter run --dart-define=LIS_BASE_URL=https://lis.swandhana.test/
```
Build aplikasi:
```bash
flutter build apk --debug
flutter build ios --debug --no-codesign
flutter build macos --debug
```
Verifikasi:
```bash
flutter analyze
flutter test
```
---
### Instalasi Listener Instrumen (`listener`)
Prasyarat:
- Python 3.
- Akses jaringan ke database LIS dan instrumen laboratorium.
- Akses port serial/TCP sesuai alat yang dipakai.
Langkah instalasi:
```bash
cd listener
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
Jalankan listener utama:
```bash
python app.py
```
Jalankan listener GeneXpert khusus:
```bash
python geneexpert.py
```
Catatan konfigurasi:
- `app.py` dan `geneexpert.py` masih menyimpan mapping IP, port, perangkat, dan database di dalam file.
- Pastikan `DATABASE_URL`, `TARGET_MAPPING`, `DEVICE_CONFIGS`, port serial, dan port TCP sesuai lingkungan produksi.
- Default port penting: TCP GeneXpert `6001` dan HTTP trigger `6002`.
---
### API Mobile Utama
Prefix endpoint:
```text
/api/mobile
```
Endpoint utama:
- `GET /api/mobile/ping`
- `POST /api/mobile/login`
- `GET /api/mobile/dashboard`
- `GET /api/mobile/critical-values`
- `POST /api/mobile/critical-values/{id}/mark-read`
- `GET /api/mobile/early-warning`
- `GET /api/mobile/examinations/search`
- `GET /api/mobile/initial-work/samples`
- `POST /api/mobile/initial-work/samples/{id}/accept`
- `POST /api/mobile/initial-work/samples/{id}/reject`
- `GET /api/mobile/books/{master}/examinations`
- `GET /api/mobile/examinations/{id}`
- `GET /api/mobile/examinations/{id}/expertise`
- `POST /api/mobile/examinations/{id}/expertise/template`
- `POST /api/mobile/examinations/{id}/expertise`
---
### Manual Book
Panduan pengguna dapat diunduh melalui:
```text
http://10.10.123.218/manual.pdf
```
---
### Dukungan Pengembangan
Aplikasi ini dikembangkan oleh **CV. Swandhana**.
Alamat:
```text
Jl. Lamandau 18A, Bunulrejo, Blimbing, Malang
```
Kontak WhatsApp:
```text
081359108565
```
Pengembangan juga didukung oleh kolaborasi antara pengembang dan AI assistant seperti ChatGPT/Gemini untuk dokumentasi, refactoring, dan percepatan pekerjaan teknis.
---
### Lisensi
Proyek ini menggunakan lisensi **MIT**. Lihat file `LICENSE` jika tersedia di distribusi project.
---
## English Version
**MyLIS-RSSA** is a Laboratory Information System ecosystem for the microbiology service at RSUD Dr. Saiful Anwar. This repository contains a Laravel web/backend application, a Flutter mobile/desktop companion app, and a Python listener service for laboratory instrument communication.
The system supports laboratory workflows from order intake, sample registration, examination grouping, expertise entry, result verification, reporting, critical value notification, to SIMRS and instrument integration.
---
### Applications in This Repository
#### 1. LIS Web & Backend (`htdocs`)
The main Laravel application for laboratory operations. It handles authentication, dashboards, patient/sample registration, examination lists, result templates, expertise, reports, labels/barcodes, logbooks, biorepository, BHP inventory, mobile APIs, cloud backup, and SIMRS integration.
Main capabilities:
- Order registration from SIMRS through `registerpasien`, `registerulang`, and `registerjson`.
- Management of patients, doctors, clinics/rooms, users, answer templates, and sub-answers.
- Microbiology worklists grouped by sample book/category.
- Result entry, result printing, addendum, labels, forms, and reports.
- Early Warning System dashboard and critical value notifications.
- PPDS/SPV logbook, biorepository, and SIMBHP modules.
- `/api/mobile` endpoints for the MyLIS Flutter app.
- Cloud database backup/restore and register API payload export/simulation utilities.
#### 2. MyLIS Flutter (`mylis`)
A mobile/desktop companion app for fast access to the Laravel LIS. It supports login, server ping, dashboard, sample search, barcode scanning, Early Warning System, initial work acceptance, examination lists, examination details, critical value notifications, and native expertise entry.
Available expertise screens:
- `CCI`
- `Kultur`
- `Pewarna Langsung`
- `TBC`
- `Viral Load`
- `IgM IgG Leptospira`
- `PCR COVID`
#### 3. Instrument Listener (`listener`)
A Python service for laboratory instrument communication, especially serial/TCP and GeneXpert workflows. It receives, processes, and sends instrument messages such as ASTM/HL7, then stores or forwards the data to the LIS database.
Main capabilities:
- TCP listener for GeneXpert.
- Serial communication for devices such as Vitek and BACTEC.
- Mapping from LIS examination names to instrument host test codes.
- Instrument order queue and result processing.
- Daily logs and per-thread logs for troubleshooting.
---
### How the System Works
Main workflow:
```text
SIMRS / web user
-> Laravel LIS receives orders and patient data
-> samples enter the microbiology worklist
-> staff accept, process, and enter results/expertise
-> supervisor verifies or finalizes the result
-> result is printed, sent, or extracted back to SIMRS
```
Mobile workflow:
```text
MyLIS Flutter
-> logs in to Laravel through /api/mobile/login
-> fetches dashboard, worklist, examination details, and expertise templates
-> saves draft, preliminary, final, or sends to SPV
-> Laravel persists the changes to the LIS database
```
Instrument workflow:
```text
Laboratory instrument
-> Python listener receives ASTM/HL7/serial/TCP messages
-> data is parsed and matched with orders/samples
-> instrument results or statuses are stored in the database
-> Laravel displays the result for validation and reporting
```
Cloud backup workflow:
```text
Production server
-> pg_dump database
-> uploads .backup file to cloud in chunks
-> restore can download the latest backup and run pg_restore
```
Detailed backup, restore, `registerApi` payload export, and SIMRS payload simulation notes are available in [htdocs/README.md](/Users/duidev/htdocs/lis/htdocs/README.md).
---
### Technology Stack
- **Backend**: Laravel 10, PHP 8.1+ with Composer platform PHP 8.4.
- **API & Auth**: Laravel API routes, encrypted token for mobile, Laravel Sanctum/Passport available in dependencies.
- **Web Frontend**: Blade, Bootstrap, jQuery, DataTables, Livewire 3, Vite.
- **Database**: `.env` supports MySQL; production/backup deployment also uses PostgreSQL for the `lismikro` database.
- **Reporting & Documents**: TCPDF, FPDI, PhpSpreadsheet, QR/barcode generator.
- **Integrations**: HL7, ASTM, serial communication, FTP, Guzzle HTTP client, BPJS bridging.
- **Mobile/Desktop**: Flutter, Dart, Material UI, HTTP client, shared preferences, URL launcher, mobile scanner.
- **Listener Engine**: Python 3, Flask, SQLAlchemy, psycopg2, pyserial, hl7.
- **Runtime Ops**: Artisan commands, cron backup, optional Docker Compose.
---
### Installing LIS Web & Backend (`htdocs`)
Requirements:
- PHP 8.1 or newer.
- Composer.
- Node.js and npm.
- MySQL or PostgreSQL database depending on the target deployment.
- PHP extensions required by Laravel and this project's packages.
Installation:
```bash
cd htdocs
composer install
npm install
cp .env.example .env
php artisan key:generate
```
Adjust the main `.env` configuration:
```env
APP_URL=https://lis.swandhana.test
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_lis
DB_USERNAME=root
DB_PASSWORD=
```
Run migrations when the target database is meant to be prepared from this project's migrations:
```bash
php artisan migrate
```
Run locally:
```bash
php artisan serve
npm run dev
```
Build production assets:
```bash
npm run build
```
Common verification commands:
```bash
php artisan route:list
php artisan route:list --path=mobile
php -l app/Http/Controllers/MobileApiController.php
```
---
### Installing MyLIS Flutter (`mylis`)
Requirements:
- Flutter SDK matching `pubspec.yaml` (`Dart SDK ^3.12.2`).
- Android Studio/Xcode depending on the target platform.
- A Laravel LIS server reachable from the device or simulator.
Installation:
```bash
cd mylis
flutter pub get
flutter run
```
Run with a specific Laravel URL:
```bash
flutter run --dart-define=LIS_BASE_URL=https://lis.swandhana.test/
```
Build:
```bash
flutter build apk --debug
flutter build ios --debug --no-codesign
flutter build macos --debug
```
Verification:
```bash
flutter analyze
flutter test
```
---
### Installing Instrument Listener (`listener`)
Requirements:
- Python 3.
- Network access to the LIS database and laboratory instruments.
- Serial/TCP port access according to the target instruments.
Installation:
```bash
cd listener
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
Run the main listener:
```bash
python app.py
```
Run the dedicated GeneXpert listener:
```bash
python geneexpert.py
```
Configuration notes:
- `app.py` and `geneexpert.py` still keep IP, port, device, and database mapping inside the files.
- Make sure `DATABASE_URL`, `TARGET_MAPPING`, `DEVICE_CONFIGS`, serial ports, and TCP ports match the production environment.
- Important default ports: GeneXpert TCP `6001` and HTTP trigger `6002`.
---
### Main Mobile API
Endpoint prefix:
```text
/api/mobile
```
Main endpoints:
- `GET /api/mobile/ping`
- `POST /api/mobile/login`
- `GET /api/mobile/dashboard`
- `GET /api/mobile/critical-values`
- `POST /api/mobile/critical-values/{id}/mark-read`
- `GET /api/mobile/early-warning`
- `GET /api/mobile/examinations/search`
- `GET /api/mobile/initial-work/samples`
- `POST /api/mobile/initial-work/samples/{id}/accept`
- `POST /api/mobile/initial-work/samples/{id}/reject`
- `GET /api/mobile/books/{master}/examinations`
- `GET /api/mobile/examinations/{id}`
- `GET /api/mobile/examinations/{id}/expertise`
- `POST /api/mobile/examinations/{id}/expertise/template`
- `POST /api/mobile/examinations/{id}/expertise`
---
### Manual Book
User manual:
```text
http://10.10.123.218/manual.pdf
```
---
### Development Support
This application is developed by **CV. Swandhana**.
Address:
```text
Jl. Lamandau 18A, Bunulrejo, Blimbing, Malang
```
WhatsApp:
```text
081359108565
```
Development is also supported by collaboration between developers and AI assistants such as ChatGPT/Gemini for documentation, refactoring, and technical acceleration.
---
### License
This project uses the **MIT** license. See the `LICENSE` file if it is available in the project distribution.
BIN
View File
Binary file not shown.
+12
View File
@@ -64,3 +64,15 @@ BPJS_CONSID="2112121"
BPJS_SECRET_KEY="121212121"
BPJS_BASE_URL="https://new-api.bpjs-kesehatan.go.id:8080"
BPJS_SERVICE_NAME="new-vclaim-rest"
CLOUD_BACKUP_API_URL=https://duidev.com
CLOUD_BACKUP_API_TOKEN=
CLOUD_BACKUP_NAME=lismikro
CLOUD_BACKUP_CHUNK_SIZE=1048576
CLOUD_BACKUP_DOWNLOAD_PATH=
CLOUD_BACKUP_PG_RESTORE=pg_restore
CLOUD_BACKUP_DB_HOST=10.10.123.193
CLOUD_BACKUP_DB_PORT=5002
CLOUD_BACKUP_DB_DATABASE=lismikro
CLOUD_BACKUP_DB_USERNAME=lismikro
CLOUD_BACKUP_DB_PASSWORD=
+271 -1
View File
@@ -47,9 +47,279 @@ Untuk panduan pengguna atau manual book dari LIS, dapat diunduh melalui link ber
- **Bootstrap** (Frontend)
- **jQuery** (Frontend)
## Backup Database ke Cloud
Backup database dijalankan dari server production `10.10.123.218`. Database yang di-backup berada di server PostgreSQL `10.10.123.193`, lalu file hasil backup `.backup` dikirim secara bertahap/chunked ke `https://duidev.com` sebagai cadangan cloud.
Alur backup:
```text
10.10.123.218 -> pg_dump dari 10.10.123.193 -> upload .backup ke duidev.com
```
Alur restore:
```text
10.10.123.218 -> download backup terbaru dari duidev.com -> pg_restore ke 10.10.123.193
```
### Konfigurasi Server Production
Tambahkan konfigurasi berikut di file `.env` server production `10.10.123.218`:
```env
CLOUD_BACKUP_API_URL=https://duidev.com
CLOUD_BACKUP_API_TOKEN=isi_token_rahasia_yang_sama_dengan_duidev
CLOUD_BACKUP_NAME=lismikro
CLOUD_BACKUP_CHUNK_SIZE=1048576
CLOUD_BACKUP_DB_HOST=10.10.123.193
CLOUD_BACKUP_DB_PORT=5002
CLOUD_BACKUP_DB_DATABASE=lismikro
CLOUD_BACKUP_DB_USERNAME=lismikro
CLOUD_BACKUP_DB_PASSWORD=
```
Jika password PostgreSQL sudah disimpan di `.pgpass`, `CLOUD_BACKUP_DB_PASSWORD` boleh dikosongkan.
Setelah mengubah `.env`, jalankan:
```bash
php artisan config:clear
```
### Password PostgreSQL Tanpa Prompt
Buat file `~/.pgpass` pada user Linux yang menjalankan cron:
```text
10.10.123.193:5002:lismikro:lismikro:PASSWORD_DATABASE
```
Lalu amankan permission-nya:
```bash
chmod 600 ~/.pgpass
```
### Script Backup Harian
Contoh file `/home/mikro/project/backup.sh`:
```bash
#!/bin/bash
echo "===== Backup mulai: $(date '+%Y-%m-%d %H:%M:%S') ====="
BACKUP_DIR="/home/mikro/project/backups"
DATE=$(date +"%Y-%m-%d_%H-%M-%S")
FILE="$BACKUP_DIR/lismikro_$DATE.backup"
mkdir -p "$BACKUP_DIR"
pg_dump -h 10.10.123.193 -p 5002 -U lismikro -d lismikro -F c -v -f "$FILE"
STATUS=$?
if [ $STATUS -eq 0 ]; then
echo "Backup berhasil: $FILE"
cd /home/mikro/project/htdocs
php artisan backup:upload-file "$FILE" --retries=10 --retry-delay=15 --timeout=300
UPLOAD_STATUS=$?
if [ $UPLOAD_STATUS -eq 0 ]; then
echo "Upload cloud berhasil"
else
echo "Upload cloud gagal dengan status: $UPLOAD_STATUS"
fi
find "$BACKUP_DIR" -type f -name "lismikro_*.backup" -mtime +2 -delete
else
echo "Backup gagal dengan status: $STATUS"
fi
echo "===== Backup selesai: $(date '+%Y-%m-%d %H:%M:%S') ====="
echo ""
```
Aktifkan script:
```bash
chmod +x /home/mikro/project/backup.sh
```
### Crontab
Jalankan backup setiap hari jam `23:59`:
```cron
59 23 * * * /home/mikro/project/backup.sh >> /home/mikro/project/backup.log 2>&1
```
Lihat log:
```bash
tail -n 100 /home/mikro/project/backup.log
```
### Upload Manual
Jika sudah memiliki file `.backup` dan ingin mengirim ulang ke cloud:
```bash
cd /home/mikro/project/htdocs
php artisan backup:upload-file /home/mikro/project/backups/lismikro_YYYY-MM-DD_HH-MM-SS.backup
```
Jika upload pernah gagal dengan HTTP `524`, gunakan chunk lebih kecil dan retry lebih banyak:
```bash
php artisan backup:upload-file /home/mikro/project/backups/lismikro_YYYY-MM-DD_HH-MM-SS.backup \
--chunk-size=1048576 \
--retries=10 \
--retry-delay=15 \
--timeout=300
```
### Download Backup Terbaru
Untuk download saja tanpa restore:
```bash
cd /home/mikro/project/htdocs
php artisan backup:restore-latest --download-only
```
### Restore Penuh ke Database
Restore dijalankan dari server production `10.10.123.218` dan target database adalah `10.10.123.193`.
```bash
cd /home/mikro/project/htdocs
php artisan backup:restore-latest
```
Jika ingin langsung restore tanpa pertanyaan konfirmasi:
```bash
php artisan backup:restore-latest --yes
```
Perintah restore memakai `pg_restore --clean --if-exists --no-owner` secara default. Pastikan `pg_restore` tersedia di server production.
## Export Reverse Payload `registerApi`
Perintah ini menarik data dari tabel `periksa` berdasarkan tanggal tertentu, lalu mengubahnya kembali menjadi JSON dengan key yang sama seperti payload awal `registerApi`.
Contoh export berdasarkan tanggal daftar:
```bash
php artisan register-api:export 2026-08-01 --pretty
```
Simpan hasil ke file:
```bash
php artisan register-api:export 2026-08-01 --pretty --output=/home/mikro/project/export-register-api-2026-08-01.json
```
Secara default filter memakai kolom `daftar`. Jika ingin memakai tanggal lain:
```bash
php artisan register-api:export 2026-08-01 --column=tanggalregis --pretty
php artisan register-api:export 2026-08-01 --column=tanggalsampel --pretty
```
Format JSON yang dihasilkan:
```json
{
"tanggal": "2026-08-01",
"filter_column": "daftar",
"total": 1,
"data": [
{
"norm": "RM001",
"nama": "Nama Pasien",
"alamat": "Alamat Pasien",
"kota": "Kota",
"tgllahir": "1990-01-01",
"nohap": "0000000000",
"kelamin": "L",
"urgensi": "Elective",
"beratbadan": "0",
"nik": "0",
"nobpjs": "0",
"drpeminta": "Dokter Peminta",
"asalpasien": "Rawat Jalan",
"diagnosa": "Diagnosa",
"statusbayar": null,
"nomor_lab": "ORDER001",
"jenispembayaran": "UMM",
"klinisi": "Dokter Peminta",
"jenispemeriksaan": "Jenis Pemeriksaan",
"spesimen": "Sputum",
"kodespesimen": "SPT",
"pengambilan": null,
"keteranganrujukan": "RSSA Malang",
"namarspengirim": "RSSA Malang",
"tanggalregis": "2026-08-01",
"tanggalsampel": "2026-08-01",
"asalpengirim": null
}
]
}
```
### Simulasi Kirim Data Seperti SIMRS
Setelah file JSON dibuat dari `register-api:export`, data dapat dikirim ulang ke endpoint `registerApi` seolah-olah berasal dari SIMRS.
Tes tanpa mengirim data:
```bash
php artisan register-api:send-export /home/mikro/project/export-register-api-2026-08-01.json --dry-run
```
Kirim ke endpoint default dari `APP_URL/registerpasien`:
```bash
php artisan register-api:send-export /home/mikro/project/export-register-api-2026-08-01.json
```
Kirim ke endpoint tertentu:
```bash
php artisan register-api:send-export /home/mikro/project/export-register-api-2026-08-01.json \
--url=http://10.10.123.218/registerpasien
```
Batasi pengiriman untuk uji coba:
```bash
php artisan register-api:send-export /home/mikro/project/export-register-api-2026-08-01.json --limit=5
```
Tambahkan jeda antar request, misalnya `500` milidetik:
```bash
php artisan register-api:send-export /home/mikro/project/export-register-api-2026-08-01.json --delay=500
```
Berhenti jika ada request gagal:
```bash
php artisan register-api:send-export /home/mikro/project/export-register-api-2026-08-01.json --stop-on-error
```
Contoh alur lengkap:
```bash
php artisan register-api:export 2026-08-01 --pretty --output=/home/mikro/project/export-register-api-2026-08-01.json
php artisan register-api:send-export /home/mikro/project/export-register-api-2026-08-01.json --url=http://10.10.123.218/registerpasien
```
## Kontribusi
Jika Anda ingin berkontribusi pada proyek ini, silakan buat **pull request** atau **issue** terkait.
## Lisensi
Proyek ini dilisensikan di bawah lisensi MIT. Lihat file **LICENSE** untuk informasi lebih lanjut.
@@ -0,0 +1,155 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Symfony\Component\Process\Process;
class BackupRestoreLatestCommand extends Command
{
protected $signature = 'backup:restore-latest
{--url= : Base URL server cloud backup}
{--token= : Token API cloud backup}
{--name= : Nama backup}
{--output= : Lokasi file download}
{--file= : Pakai file backup lokal, tanpa download}
{--download-only : Hanya download, tidak restore}
{--host= : Host PostgreSQL target}
{--port= : Port PostgreSQL target}
{--database= : Database PostgreSQL target}
{--username= : Username PostgreSQL target}
{--password= : Password PostgreSQL target, opsional jika .pgpass sudah ada}
{--clean=1 : Jalankan pg_restore --clean}
{--if-exists=1 : Jalankan pg_restore --if-exists}
{--no-owner=1 : Jalankan pg_restore --no-owner}
{--yes : Lewati konfirmasi restore}';
protected $description = 'Download backup terbaru dari duidev.com dan restore penuh ke PostgreSQL.';
public function handle(): int
{
$backupName = (string) ($this->option('name') ?: config('cloud_backup.backup_name', 'lismikro'));
$file = $this->option('file')
? (string) $this->option('file')
: $this->downloadLatest($backupName);
if (!$file || !File::exists($file)) {
$this->error('File backup tidak ditemukan.');
return Command::FAILURE;
}
$this->info('File backup siap: ' . $file);
if ($this->option('download-only')) {
return Command::SUCCESS;
}
$host = (string) ($this->option('host') ?: config('cloud_backup.db_host'));
$port = (string) ($this->option('port') ?: config('cloud_backup.db_port'));
$database = (string) ($this->option('database') ?: config('cloud_backup.db_database'));
$username = (string) ($this->option('username') ?: config('cloud_backup.db_username'));
if (!$this->option('yes') && !$this->confirm("Restore penuh ke database {$database} di {$host}:{$port}?", false)) {
$this->warn('Restore dibatalkan.');
return Command::FAILURE;
}
$command = [
(string) config('cloud_backup.pg_restore', 'pg_restore'),
'-h', $host,
'-p', $port,
'-U', $username,
'-d', $database,
'-v',
];
if ((int) $this->option('clean') === 1) {
$command[] = '--clean';
}
if ((int) $this->option('if-exists') === 1) {
$command[] = '--if-exists';
}
if ((int) $this->option('no-owner') === 1) {
$command[] = '--no-owner';
}
$command[] = $file;
$env = null;
$password = (string) ($this->option('password') ?: config('cloud_backup.db_password'));
if ($password !== '') {
$env = ['PGPASSWORD' => $password];
}
$process = new Process($command, base_path(), $env, null, null);
$process->setTimeout(null);
$this->info('Menjalankan pg_restore...');
$process->run(function (string $type, string $buffer): void {
$this->output->write($buffer);
});
if (!$process->isSuccessful()) {
$this->error('Restore gagal dengan kode: ' . $process->getExitCode());
return Command::FAILURE;
}
$this->info('Restore selesai.');
return Command::SUCCESS;
}
private function downloadLatest(string $backupName): ?string
{
$baseUrl = rtrim((string) ($this->option('url') ?: config('cloud_backup.api_url')), '/');
$token = (string) ($this->option('token') ?: config('cloud_backup.api_token'));
if ($baseUrl === '' || $token === '') {
$this->error('CLOUD_BACKUP_API_URL dan CLOUD_BACKUP_API_TOKEN wajib diisi.');
return null;
}
$output = $this->option('output')
? (string) $this->option('output')
: (string) config('cloud_backup.download_path');
File::ensureDirectoryExists(dirname($output), 0750, true);
$metadata = Http::withToken($token)
->timeout(60)
->get($baseUrl . '/api/backups/latest', ['backup_name' => $backupName]);
if (!$metadata->successful()) {
$this->error('Gagal membaca metadata backup terbaru. HTTP ' . $metadata->status());
$this->line($metadata->body());
return null;
}
$info = $metadata->json();
$this->info('Backup terbaru: ' . ($info['file'] ?? '-'));
$download = Http::withToken($token)
->timeout(0)
->sink($output)
->get($baseUrl . '/api/backups/latest/download', ['backup_name' => $backupName]);
if (!$download->successful()) {
File::delete($output);
$this->error('Gagal download backup. HTTP ' . $download->status());
$this->line($download->body());
return null;
}
if (!empty($info['sha256']) && hash_file('sha256', $output) !== strtolower((string) $info['sha256'])) {
File::delete($output);
$this->error('Checksum file download tidak sesuai.');
return null;
}
return $output;
}
}
@@ -0,0 +1,145 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class BackupUploadFileCommand extends Command
{
protected $signature = 'backup:upload-file
{file : Path file .backup yang akan dikirim}
{--url= : Base URL server cloud backup}
{--token= : Token API cloud backup}
{--name= : Nama backup}
{--chunk-size= : Ukuran chunk dalam byte}
{--retries=5 : Jumlah percobaan ulang setiap chunk}
{--retry-delay=10 : Jeda percobaan ulang dalam detik}
{--timeout=300 : Timeout request upload dalam detik}';
protected $description = 'Upload file backup ke duidev.com secara bertahap/chunked.';
public function handle(): int
{
$file = (string) $this->argument('file');
if (!is_file($file) || !is_readable($file)) {
$this->error('File backup tidak ditemukan atau tidak bisa dibaca: ' . $file);
return Command::FAILURE;
}
$baseUrl = rtrim((string) ($this->option('url') ?: config('cloud_backup.api_url')), '/');
$token = (string) ($this->option('token') ?: config('cloud_backup.api_token'));
$backupName = (string) ($this->option('name') ?: config('cloud_backup.backup_name', 'lismikro'));
$chunkSize = (int) ($this->option('chunk-size') ?: config('cloud_backup.chunk_size', 5 * 1024 * 1024));
$retries = max(1, (int) $this->option('retries'));
$retryDelay = max(0, (int) $this->option('retry-delay'));
$timeout = max(30, (int) $this->option('timeout'));
if ($baseUrl === '' || $token === '') {
$this->error('CLOUD_BACKUP_API_URL dan CLOUD_BACKUP_API_TOKEN wajib diisi.');
return Command::FAILURE;
}
if ($chunkSize < 1024 * 1024) {
$this->error('Chunk size minimal 1 MB.');
return Command::FAILURE;
}
$size = filesize($file);
$checksum = hash_file('sha256', $file);
$totalChunks = (int) ceil($size / $chunkSize);
$uploadId = now()->format('YmdHis') . '_' . Str::random(16);
$endpoint = $baseUrl . '/api/backups/chunk';
$this->info('Upload backup: ' . basename($file));
$this->info('Upload ID: ' . $uploadId);
$this->info('Total chunk: ' . $totalChunks);
$handle = fopen($file, 'rb');
if (!$handle) {
$this->error('Gagal membuka file backup.');
return Command::FAILURE;
}
try {
for ($index = 0; $index < $totalChunks; $index++) {
$chunk = fread($handle, $chunkSize);
if ($chunk === false) {
$this->error('Gagal membaca chunk ke-' . $index);
return Command::FAILURE;
}
$response = $this->uploadChunk(
endpoint: $endpoint,
token: $token,
chunk: $chunk,
chunkIndex: $index,
payload: [
'backup_name' => $backupName,
'upload_id' => $uploadId,
'filename' => basename($file),
'chunk_index' => $index,
'total_chunks' => $totalChunks,
'total_size' => $size,
'checksum' => $checksum,
],
retries: $retries,
retryDelay: $retryDelay,
timeout: $timeout
);
if (!$response || !$response->successful()) {
$this->error('Upload chunk ke-' . $index . ' gagal. HTTP ' . ($response?->status() ?: '-'));
$this->line($response?->body() ?: 'Tidak ada response dari server.');
return Command::FAILURE;
}
$this->line('Chunk ' . ($index + 1) . '/' . $totalChunks . ' terkirim.');
}
} finally {
fclose($handle);
}
$this->info('Upload backup selesai.');
return Command::SUCCESS;
}
private function uploadChunk(
string $endpoint,
string $token,
string $chunk,
int $chunkIndex,
array $payload,
int $retries,
int $retryDelay,
int $timeout
): ?Response {
$response = null;
for ($attempt = 1; $attempt <= $retries; $attempt++) {
try {
$response = Http::withToken($token)
->timeout($timeout)
->attach('chunk', $chunk, 'chunk_' . $chunkIndex . '.part')
->post($endpoint, $payload);
if ($response->successful()) {
return $response;
}
$this->warn('Chunk ' . ($chunkIndex + 1) . ' gagal percobaan ' . $attempt . '/' . $retries . '. HTTP ' . $response->status());
} catch (\Throwable $e) {
$this->warn('Chunk ' . ($chunkIndex + 1) . ' gagal percobaan ' . $attempt . '/' . $retries . ': ' . $e->getMessage());
}
if ($attempt < $retries && $retryDelay > 0) {
sleep($retryDelay);
}
}
return $response;
}
}
@@ -0,0 +1,138 @@
<?php
namespace App\Console\Commands;
use App\Periksa;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
class ExportRegisterApiByDateCommand extends Command
{
protected $signature = 'register-api:export
{date : Tanggal data periksa, format YYYY-MM-DD}
{--column=daftar : Kolom tanggal untuk filter, contoh daftar/tanggalregis/tanggalsampel}
{--output= : Simpan hasil JSON ke file}
{--pretty : Format JSON agar mudah dibaca}';
protected $description = 'Export data periksa per tanggal ke format JSON payload registerApi.';
public function handle(): int
{
$date = $this->argument('date');
$column = (string) $this->option('column');
if (!$this->isValidDate($date)) {
$this->error('Format tanggal harus YYYY-MM-DD.');
return Command::FAILURE;
}
if (!in_array($column, ['daftar', 'tanggalregis', 'tanggalsampel'], true)) {
$this->error('Kolom tanggal hanya boleh: daftar, tanggalregis, tanggalsampel.');
return Command::FAILURE;
}
$records = Periksa::with('getPasien')
->whereDate($column, $date)
->orderBy($column)
->orderBy('id')
->get()
->map(fn (Periksa $periksa) => $this->mapPeriksaToRegisterPayload($periksa))
->values();
$payload = [
'tanggal' => $date,
'filter_column' => $column,
'total' => $records->count(),
'data' => $records,
];
$jsonOptions = JSON_UNESCAPED_SLASHES;
if ($this->option('pretty')) {
$jsonOptions |= JSON_PRETTY_PRINT;
}
$json = json_encode($payload, $jsonOptions);
if ($json === false) {
$this->error('Gagal membuat JSON.');
return Command::FAILURE;
}
if ($this->option('output')) {
$output = (string) $this->option('output');
File::ensureDirectoryExists(dirname($output));
File::put($output, $json . PHP_EOL);
$this->info('Export selesai: ' . $output);
$this->info('Total data: ' . $records->count());
return Command::SUCCESS;
}
$this->line($json);
return Command::SUCCESS;
}
private function mapPeriksaToRegisterPayload(Periksa $periksa): array
{
$pasien = $periksa->getPasien;
$tanggalLahir = $this->formatDate($periksa->tgllahirpasien ?? $pasien?->tgl_lahir);
$tanggalRegis = $this->formatDate($periksa->tanggalregis) ?: $this->formatDate($periksa->daftar);
$tanggalSampel = $this->formatDate($periksa->tanggalsampel) ?: $tanggalRegis;
$diagnosa = $periksa->klinis ?: null;
$klinisi = $periksa->klinisi ?: null;
return [
'norm' => $periksa->noregister,
'nama' => $periksa->nmpasien,
'alamat' => $periksa->alamatpasien ?: config('global.addressapps'),
'kota' => $pasien?->kota ?: config('global.subdomainapps'),
'tgllahir' => $tanggalLahir ?: date('Y-m-d'),
'nohap' => $periksa->tlppasien ?: '0000000000',
'kelamin' => $periksa->jkpasien ?: 'L',
'urgensi' => $periksa->urgensi ?: 'Elective',
'beratbadan' => (string) ($periksa->berat ?? '0'),
'nik' => $periksa->ktp ?: '0',
'nobpjs' => $periksa->bpjs ?: '0',
'drpeminta' => $klinisi,
'asalpasien' => $periksa->asalpasien,
'diagnosa' => $diagnosa,
'statusbayar' => null,
'nomor_lab' => $periksa->orderid,
'jenispembayaran' => $periksa->asuransi ?: 'UMM',
'klinisi' => $klinisi,
'jenispemeriksaan' => $periksa->reques,
'spesimen' => $periksa->nm_spesimen,
'kodespesimen' => $periksa->kd_spesimen,
'pengambilan' => $periksa->pengambilan,
'keteranganrujukan' => $periksa->nmrs ?: 'RSSA Malang',
'namarspengirim' => $periksa->nmrs ?: 'RSSA Malang',
'tanggalregis' => $tanggalRegis ?: date('Y-m-d'),
'tanggalsampel' => $tanggalSampel ?: date('Y-m-d'),
'asalpengirim' => $periksa->asalpengirim,
];
}
private function isValidDate(string $date): bool
{
try {
$parsed = Carbon::createFromFormat('Y-m-d', $date);
} catch (\Throwable) {
return false;
}
return $parsed && $parsed->format('Y-m-d') === $date;
}
private function formatDate(mixed $value): ?string
{
if (!$value) {
return null;
}
try {
return Carbon::parse($value)->format('Y-m-d');
} catch (\Throwable) {
return null;
}
}
}
@@ -0,0 +1,115 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class SendRegisterApiExportCommand extends Command
{
protected $signature = 'register-api:send-export
{file : Path file JSON hasil register-api:export}
{--url= : URL endpoint registerApi, default APP_URL/registerpasien}
{--delay=0 : Jeda antar kirim dalam milidetik}
{--limit=0 : Batasi jumlah data yang dikirim, 0 berarti semua}
{--start=0 : Mulai dari index data ke-n, 0 berarti dari awal}
{--dry-run : Tampilkan payload tanpa mengirim}
{--stop-on-error : Berhenti jika ada request gagal}';
protected $description = 'Simulasi kirim payload registerApi dari file JSON export seolah-olah dikirim oleh SIMRS.';
public function handle(): int
{
$file = (string) $this->argument('file');
if (!File::exists($file) || !is_readable($file)) {
$this->error('File JSON tidak ditemukan atau tidak bisa dibaca: ' . $file);
return Command::FAILURE;
}
$content = File::get($file);
$decoded = json_decode($content, true);
if (!is_array($decoded)) {
$this->error('File bukan JSON valid.');
return Command::FAILURE;
}
$rows = $this->extractRows($decoded);
if (empty($rows)) {
$this->warn('Tidak ada data untuk dikirim.');
return Command::SUCCESS;
}
$start = max(0, (int) $this->option('start'));
$limit = max(0, (int) $this->option('limit'));
$delay = max(0, (int) $this->option('delay'));
$endpoint = $this->endpoint();
$rows = array_slice($rows, $start, $limit > 0 ? $limit : null);
$total = count($rows);
$success = 0;
$failed = 0;
$this->info('Endpoint: ' . $endpoint);
$this->info('Total payload: ' . $total);
foreach ($rows as $index => $payload) {
$sequence = $start + $index + 1;
$label = $payload['nomor_lab'] ?? $payload['norm'] ?? $sequence;
if ($this->option('dry-run')) {
$this->line('[' . $sequence . '] DRY RUN ' . $label . ': ' . json_encode($payload, JSON_UNESCAPED_SLASHES));
$success++;
continue;
}
$response = Http::asJson()
->acceptJson()
->timeout(120)
->post($endpoint, $payload);
if ($response->successful()) {
$success++;
$this->line('[' . $sequence . '] OK ' . $label . ' HTTP ' . $response->status() . ' ' . $response->body());
} else {
$failed++;
$this->error('[' . $sequence . '] GAGAL ' . $label . ' HTTP ' . $response->status() . ' ' . $response->body());
if ($this->option('stop-on-error')) {
break;
}
}
if ($delay > 0 && $index < $total - 1) {
usleep($delay * 1000);
}
}
$this->info('Selesai. Berhasil: ' . $success . ', gagal: ' . $failed);
return $failed > 0 ? Command::FAILURE : Command::SUCCESS;
}
private function extractRows(array $decoded): array
{
if (isset($decoded['data']) && is_array($decoded['data'])) {
return $decoded['data'];
}
if (array_is_list($decoded)) {
return $decoded;
}
return [$decoded];
}
private function endpoint(): string
{
$url = (string) $this->option('url');
if ($url !== '') {
return $url;
}
return rtrim((string) config('app.url'), '/') . '/registerpasien';
}
}
@@ -0,0 +1,124 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class SyncPoliFromJson extends Command
{
protected $signature = 'poli:sync-json {--path= : Path file JSON poli, default database/seeders/data/poli.json} {--dry-run : Cek data tanpa menulis ke database}';
protected $description = 'Update atau create data poli dari file JSON berdasarkan id tanpa truncate.';
public function handle(): int
{
$path = $this->resolvePath($this->option('path') ?: database_path('seeders/data/poli.json'));
$data = $this->readJson($path);
$updated = 0;
$created = 0;
$unchanged = 0;
DB::transaction(function () use ($data, &$updated, &$created, &$unchanged) {
foreach ($data as $row) {
$id = (int) $row['id'];
$payload = [
'poli' => $row['poli'] ?? null,
'subpoli' => $row['subpoli'] ?? null,
'subsubpoli' => $row['subsubpoli'] ?? null,
'modaliti' => $row['modaliti'] ?? null,
'kelompok' => $row['kelompok'] ?? null,
'modaliti2' => isset($row['modaliti2']) ? (string) $row['modaliti2'] : null,
'created_at' => $row['created_at'] ?? now(),
'updated_at' => $row['updated_at'] ?? now(),
];
$existing = DB::table('poli')->where('id', $id)->first();
if ($existing === null) {
$created++;
if (!$this->option('dry-run')) {
DB::table('poli')->insert(array_merge(['id' => $id], $payload));
}
continue;
}
if ($this->isSameRow($existing, $payload)) {
$unchanged++;
continue;
}
$updated++;
if (!$this->option('dry-run')) {
DB::table('poli')->where('id', $id)->update($payload);
}
}
});
$mode = $this->option('dry-run') ? 'DRY RUN' : 'DONE';
$this->info("{$mode}: {$created} created, {$updated} updated, {$unchanged} unchanged.");
return self::SUCCESS;
}
private function resolvePath(string $path): string
{
if ($path !== '' && $path[0] === '/') {
return $path;
}
return base_path($path);
}
private function readJson(string $path): array
{
if (!is_file($path)) {
throw new \RuntimeException("File JSON poli tidak ditemukan: {$path}");
}
$json = file_get_contents($path);
if ($json === false) {
throw new \RuntimeException("Gagal membaca file JSON poli: {$path}");
}
$data = json_decode($json, true);
if (!is_array($data)) {
throw new \RuntimeException("Format JSON poli tidak valid: {$path}");
}
$ids = [];
foreach ($data as $index => $row) {
if (!is_array($row) || !isset($row['id'])) {
throw new \RuntimeException('Data poli baris '.($index + 1).' tidak memiliki id.');
}
$id = (int) $row['id'];
if (isset($ids[$id])) {
throw new \RuntimeException("Duplikasi id poli pada JSON: {$id}");
}
$ids[$id] = true;
}
return $data;
}
private function isSameRow(object $existing, array $payload): bool
{
foreach ($payload as $key => $value) {
if ((string) ($existing->{$key} ?? '') !== (string) ($value ?? '')) {
return false;
}
}
return true;
}
}
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\BioCabinet;
use App\BioRack;
use App\BioSpecimen;
use App\Periksa;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Session;
@@ -28,15 +29,6 @@ class BiorepositoryController extends Controller
->orderBy('id', 'DESC')
->get();
$data['cabinets'] = BioCabinet::with([
'racks' => function ($query) {
$query->orderBy('rack_number', 'ASC')->orderBy('code', 'ASC');
},
'racks.specimens' => function ($query) {
$query->orderBy('stored_at', 'ASC');
},
])->orderBy('name', 'ASC')->get();
$data['oldestSpecimen'] = BioSpecimen::with(['cabinet', 'rack'])
->whereNotNull('stored_at')
->orderBy('stored_at', 'ASC')
@@ -79,7 +71,9 @@ class BiorepositoryController extends Controller
'cabinet_id' => 'required|exists:bio_cabinets,id',
'code' => 'required|max:50',
'name' => 'required|max:150',
'slot_count' => 'required|integer|min:1|max:20',
'level' => 'required|integer|min:4|max:6',
'box_number' => 'required|integer|min:1|max:20',
'capacity' => 'required|integer|min:16|max:100',
'notes' => 'nullable',
]);
@@ -102,9 +96,10 @@ class BiorepositoryController extends Controller
'cabinet_id' => $request->input('cabinet_id'),
'code' => $request->input('code'),
'name' => $request->input('name'),
'slot_count' => $request->input('slot_count'),
'level' => $request->input('level'),
'rack_number' => $nextRackNumber,
'box_number' => 1,
'box_number' => $request->input('box_number'),
'capacity' => $request->input('capacity'),
'notes' => $request->input('notes'),
]);
@@ -136,15 +131,23 @@ class BiorepositoryController extends Controller
$rack = BioRack::find($request->input('rack_id'));
$expectedRackNo = $rack->rack_number ?: $rack->id;
$expectedBoxNo = $rack->box_number ?: 1;
$slotCount = (int) ($rack->slot_count ?: 1);
$boxCount = (int) ($rack->box_number ?: 1);
if ((int) $request->input('raknomor') !== (int) $expectedRackNo ||
(int) $request->input('boxnomor') !== (int) $expectedBoxNo) {
if ((int) $request->input('raknomor') !== (int) $expectedRackNo) {
return back()->withErrors(['slotnomor' => 'Posisi slot tidak sesuai dengan konfigurasi rack.'])->withInput();
}
if ((int) $request->input('slotnomor') < 1 || (int) $request->input('slotnomor') > $slotCount) {
return back()->withErrors(['slotnomor' => 'Slot melebihi konfigurasi rack.'])->withInput();
}
if ((int) $request->input('shelfnomor') < 1 || (int) $request->input('shelfnomor') > (int) $rack->level) {
return back()->withErrors(['shelfnomor' => 'Shelf melebihi konfigurasi rack.'])->withInput();
return back()->withErrors(['shelfnomor' => 'Shelf melebihi konfigurasi slot.'])->withInput();
}
if ((int) $request->input('boxnomor') < 1 || (int) $request->input('boxnomor') > $boxCount) {
return back()->withErrors(['boxnomor' => 'Box melebihi konfigurasi shelf.'])->withInput();
}
if ((int) $request->input('tubenomor') < 1 || (int) $request->input('tubenomor') > (int) $rack->capacity) {
@@ -152,7 +155,9 @@ class BiorepositoryController extends Controller
}
$slotTaken = BioSpecimen::where('rack_id', $rack->id)
->where('slot_number', $request->input('slotnomor'))
->where('shelf_number', $request->input('shelfnomor'))
->where('box_number', $request->input('boxnomor'))
->where('tube_number', $request->input('tubenomor'))
->first();
@@ -209,7 +214,7 @@ class BiorepositoryController extends Controller
$sampleid = $request->input('sampleid');
if ($sampleid) {
$cekdata = Periksa::where('nofoto', $sampleid)->first();
if (!$cekdata) {
if ($cekdata) {
$sampleid = $cekdata->id;
} else {
$sampleid = null;
@@ -226,7 +231,7 @@ class BiorepositoryController extends Controller
'category_storage' => $request->input('kategorisimpan'),
'shelf_number' => $request->input('shelfnomor'),
'rack_number' => $request->input('raknomor'),
'slot_number' => $request->input('shelfnomor'),
'slot_number' => $request->input('slotnomor'),
'box_number' => $request->input('boxnomor'),
'tube_number' => $request->input('tubenomor'),
'bacteria_name' => $request->input('nmbakteri'),
File diff suppressed because it is too large Load diff
@@ -1122,7 +1122,7 @@ class FrontpageController extends Controller
$nik = $request->input('nik') ?? 0;
$bpjs = $request->input('bpjs') ?? 0;
$tlsasal = $request->input('asalpasien');
$nmrs = $request->input('namarspengirim') ?? 'RSSA Malang';
$nmrs = $request->input('namarspengirim') ?? $request->input('keteranganrujukan') ?? 'RSSA Malang';
$klinisi = $request->input('drpeminta');
$insurance = $request->input('asuransi') ?? 'UMM';
$rekues = $request->input('layanan');
@@ -1207,6 +1207,7 @@ class FrontpageController extends Controller
$noloket = $noloket + 1;
}
$code_poli = 'Mikrobiologi';
$dlp = '';
if ($rekues == '' OR is_null($rekues)){
$poli_id = 0;
} else {
@@ -1215,6 +1216,7 @@ class FrontpageController extends Controller
$poli_id = $check->id;
$kd_spesimen= $check->subsubpoli;
$nm_spesimen= $check->modaliti;
$dlp = $check->kelompok;
} else {
$poli_id = Poli::insertGetId([
'poli' => 'Mikrobiologi',
@@ -1263,6 +1265,7 @@ class FrontpageController extends Controller
'nm_spesimen' => $nm_spesimen,
'keterangan' => '',
'kesimpulan' => '',
'dlp' => $dlp,
'asuransi' => $insurance,
'urgensi' => $urgensi,
'daftar' => $mulai,
@@ -1271,7 +1274,7 @@ class FrontpageController extends Controller
'orderid' => $notransaksi,
]);
$pesan = $nofoto;
$daftaridtcm = [79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
$daftaridtcm = [77,78,79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
if (in_array($poli_id, $daftaridtcm)){
$genexpert = false;
} else {
@@ -1337,7 +1340,7 @@ class FrontpageController extends Controller
$nm_spesimen = $decodedData['spesimen'] ?? null;
$kd_spesimen = $decodedData['kodespesimen'] ?? null;
$pengambilan = $decodedData['pengambilan'] ?? null;
$nmrs = $decodedData['keteranganrujukan'] ?? 'RSSA Malang';
$nmrs = $decodedData['keteranganrujukan'] ?? $decodedData['namarspengirim'] ?? 'RSSA Malang';
$tanggalregis = $decodedData['tanggalregis'] ?? date('Y-m-d');
$tanggalsampel = $decodedData['tanggalsampel'] ?? date('Y-m-d');
$asalpengirim = $decodedData['asalpengirim'] ?? null;
@@ -1429,11 +1432,13 @@ class FrontpageController extends Controller
$noloket = $noloket + 1;
}
$code_poli = 'Mikrobiologi';
$dlp = '';
$check = Poli::where('subpoli', $rekues)->first();
if (isset($check->id)){
$poli_id = $check->id;
$kd_spesimen= $check->subsubpoli;
$nm_spesimen= $check->modaliti;
$dlp = $check->kelompok;
} else {
$poli_id = Poli::insertGetId([
'poli' => 'Mikrobiologi',
@@ -1489,6 +1494,7 @@ class FrontpageController extends Controller
'asuransi' => $insurance,
'urgensi' => $urgensi,
'daftar' => $mulai,
'dlp' => $dlp,
'pengambilan' => $pengambilan,
'tanggalregis' => $tanggalregis,
'tanggalsampel' => $tanggalsampel,
@@ -1498,7 +1504,7 @@ class FrontpageController extends Controller
'orderid' => $notransaksi,
]);
$pesan = $nofoto;
$daftaridtcm = [79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
$daftaridtcm = [77,78,79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
if (in_array($poli_id, $daftaridtcm)){
$genexpert = false;
} else {
@@ -1536,6 +1542,191 @@ class FrontpageController extends Controller
}
return response()->json(['status' => 'Sukses', 'message' => $pesan], 201);
}
public function registerApiUlang(Request $request) {
Log::info(' Incomming '.json_encode($request->input()));
$decodedData = $request->json()->all();
$norm = $decodedData['norm'] ?? null;
$nama = $decodedData['nama'] ?? null;
$alamat = $decodedData['alamat'] ?? config('global.addressapps');
$kota = $decodedData['kota'] ?? config('global.subdomainapps');
$tgllahir = $decodedData['tgllahir'] ?? date('Y-m-d');
$nohap = $decodedData['nohap'] ?? '0000000000';
$jk = $decodedData['kelamin'] ?? 'L';
$urgensi = $decodedData['urgensi'] ?? 'Elective';
$berat = $decodedData['beratbadan'] ?? '0';
$nik = $decodedData['nik'] ?? '0';
$bpjs = $decodedData['nobpjs'] ?? '0';
$drpeminta = $decodedData['drpeminta'] ?? null;
$tlsasal = $decodedData['asalpasien'] ?? null;
$diagnosa = $decodedData['diagnosa'] ?? null;
$statusbayar = $decodedData['statusbayar'] ?? null;
$notransaksi = $decodedData['nomor_lab'] ?? null;
$insurance = $decodedData['jenispembayaran'] ?? 'UMM';
$klinis = $decodedData['diagnosa'] ?? $decodedData['klinisi'] ?? null;
$klinisi = $decodedData['klinisi'] ?? $drpeminta;
$rekues = $decodedData['jenispemeriksaan'] ?? null;
$nm_spesimen = $decodedData['spesimen'] ?? null;
$kd_spesimen = $decodedData['kodespesimen'] ?? null;
$pengambilan = $decodedData['pengambilan'] ?? null;
$nmrs = $decodedData['keteranganrujukan'] ?? $decodedData['namarspengirim'] ?? 'RSSA Malang';
$tanggalregis = $decodedData['tanggalregis'] ?? date('Y-m-d');
$tanggalsampel = $decodedData['tanggalsampel'] ?? date('Y-m-d');
$asalpengirim = $decodedData['asalpengirim'] ?? null;
$nofoto = $decodedData['nomorsampel'] ?? '';
$telpon = str_replace('_', '', $nohap);
$tlsasal = str_replace('_', ' ', $tlsasal);
$klinisi = str_replace('_', ' ', $klinisi);
$nama = str_replace('_', ' ', $nama);
$alamat = str_replace('_', ' ', $alamat);
$iddokter = '';
$ruangan = 1;
$kelompok = 'admin';
$daftar = date('Y-m-d H:i:s');
$mulai = $daftar;
$tambah = ' + 360 second';
$akhir = date('Y-m-d h:i:s',strtotime($tambah,strtotime($daftar)));
$start = strtotime(date("Y-m-d H:i:s",strtotime($mulai)));
$finish = strtotime(date("Y-m-d H:i:s",strtotime($akhir)));
$tglsekarang = date('Y-m-d');
$noregister = strtoupper($norm);
$yy = date("Y");
$mm = date("m");
$terjadwal = 'sekarang';
$kamar = 'Mikrobiologi';
$pasien_id = 0;
$tgllahir = \Carbon\Carbon::createFromFormat('Y-m-d', $tgllahir);
$pasien = Pasien::where('norm', $noregister)->first();
try {
if (isset($pasien->id)) {
$pasien_id = $pasien->id;
Pasien::where('norm', $noregister)->update([
'nama' => $nama,
'jk' => $jk,
'tgl_lahir' => $tgllahir,
'kota' => $kota,
'telpon' => $telpon,
'nik' => $nik,
'bpjs' => $bpjs,
]);
} else {
$alamatbaru = substr($alamat, 0, 100);
$pasien_id = Pasien::insertGetId([
'norm' => $noregister,
'nama' => $nama,
'jk' => $jk,
'tgl_lahir' => $tgllahir,
'kota' => $kota,
'telpon' => $telpon,
'alamat' => $alamatbaru,
'nik' => $nik,
'bpjs' => $bpjs,
]);
}
} catch (\Exception $e) {
Log::error('Error in register Pasien: '.$e->getMessage());
}
$datetime = new DateTime($tgllahir);
$today = new DateTime();
$diff = $today->diff($datetime);
$tahun = $diff->y;
$bulan = $diff->m;
if ($tahun ==0) {
$usia = $bulan." Bulan";
} else {
$usia = $tahun." Tahun, ". $bulan. " Bulan";
}
$jruang = Ruangan::where('id', $ruangan)->first();
if (isset($jruang->ruangan)){
$kamar = $jruang->ruangan;
} else { $kamar = '-'; $ruangan = 1; }
$tahuncari = date("Y");
$noloketsaja = explode('-', $nofoto);
$noloket = $noloketsaja[1] ?? 0;
$noloket = (int)$noloket;
$code_poli = 'Mikrobiologi';
$dlp = '';
$check = Poli::where('subpoli', $rekues)->first();
if (isset($check->id)){
$poli_id = $check->id;
$kd_spesimen= $check->subsubpoli;
$nm_spesimen= $check->modaliti;
$dlp = $check->kelompok;
} else {
$poli_id = Poli::insertGetId([
'poli' => 'Mikrobiologi',
'subpoli' => $rekues,
'subsubpoli' => $kd_spesimen,
'modaliti' => $nm_spesimen,
]);
}
if ($iddokter == ''){
$iddokter = 0;
$dokter = null;
} else {
$getdokter = User::where('id', $iddokter)->first();
if (isset($getdokter->nama)){
$dokter = $getdokter->nama;
} else { $dokter = 'Dokter Spesialis'; }
}
$clientIp = $request->server('HTTP_X_FORWARDED_FOR') ?: $request->ip();
$clientIpNumeric = ip2long($clientIp);
if ($nofoto == ''){
return response()->json(['status' => 'Gagal', 'message' => 'Nomor Sample Tidak Boleh Kosong'], 500);
} else {
$ceksudahada = Periksa::where('noregister', $noregister)->where('orderid', $notransaksi)->where('poli_id', $poli_id)->first();
if (isset($ceksudahada->id)){
$pesan = $ceksudahada->nofoto;
} else {
$periksaid = Periksa::insertGetId([
'mulai' => $tanggalsampel,
'akhir' => $tanggalsampel,
'noloket' => $noloket,
'nofoto' => $nofoto,
'noregister' => $noregister,
'asalpasien' => $tlsasal,
'nmrs' => $nmrs,
'pasien_id' => $pasien_id,
'nmpasien' => $nama,
'jkpasien' => $jk,
'ktp' => $nik,
'bpjs' => $bpjs,
'tgllahirpasien'=> $tgllahir,
'tlppasien' => $telpon,
'alamatpasien' => $alamat,
'reques' => $rekues,
'usia' => $usia,
'berat' => $berat,
'ruangan_id' => $ruangan,
'ruangan' => $kamar,
'klinisi' => $klinisi,
'klinis' => $klinis,
'poli_id' => $poli_id,
'kd_spesimen' => $kd_spesimen,
'nm_spesimen' => $nm_spesimen,
'keterangan' => '',
'kesimpulan' => $decodedData['spesimen'] ?? $nm_spesimen ?? null,
'asuransi' => $insurance,
'urgensi' => $urgensi,
'daftar' => $tanggalsampel,
'dlp' => $dlp,
'pengambilan' => $pengambilan,
'tanggalregis' => $tanggalregis,
'tanggalsampel' => $tanggalsampel,
'pendaftar' => $clientIpNumeric,
'nmpendaftar' => $clientIp,
'asalpengirim' => $asalpengirim,
'orderid' => $notransaksi,
]);
$pesan = $nofoto;
}
return response()->json(['status' => 'Sukses', 'message' => $pesan], 201);
}
}
public function register(Request $request) {
$validator = Validator::make($request->all(), [
@@ -1704,6 +1895,7 @@ class FrontpageController extends Controller
$subpoli = $check->subpoli ?? '';
$modality = $check->modaliti ?? 'OT';
$modaliti2 = $check->modaliti2 ?? '';
$dlp = $check->kelompok ?? '';
if (isset($check->subsubpoli)){
$rekues = $subpoli.' - '.$check->subsubpoli;
} else {
@@ -1752,6 +1944,7 @@ class FrontpageController extends Controller
'asuransi' => $insurance,
'urgensi' => $urgensi,
'daftar' => $mulai,
'dlp' => $dlp,
'kd_spesimen' => $kd_spesimen,
'nm_spesimen' => $nm_spesimen,
'pendaftar' => Session('previlage'),
@@ -347,6 +347,238 @@ class GudangController extends Controller
return response()->json($response, 200);
}
public function jsonExpiredBatches(Request $request)
{
if (Session::get('previlage') == '') {
return response()->json(['message' => 'Unauthorized'], 401);
}
$jenis = trim((string) $request->query('jenis', ''));
$query = SIMBHPReport::whereNotNull('pemasukan')
->where('pemasukan', '>', 0);
if ($jenis !== '') {
$query->where('jenis', $jenis);
}
$rows = $query->orderByRaw('masa_expired IS NULL ASC')
->orderBy('masa_expired', 'ASC')
->orderBy('id', 'DESC')
->limit(100)
->get();
$items = [];
foreach ($rows as $row) {
$items[] = [
'id' => $row->id,
'jenis' => $row->jenis,
'deskripsi' => $row->deskripsi ?: '-',
'tanggal_masuk' => sprintf('%02d-%02d-%04d', (int) $row->tanggal, (int) $row->bulan, (int) $row->tahun),
'qty' => (int) ($row->pemasukan ?? 0),
'satuan' => $row->satuan_transaksi ?: 'besar',
'masa_expired' => $row->masa_expired ? date('d-m-Y', strtotime((string) $row->masa_expired)) : '',
];
}
return response()->json(['data' => $items]);
}
public function updateExpiredBatch(Request $request)
{
if (Session::get('previlage') == '') {
return response()->json(['message' => 'Unauthorized'], 401);
}
$batchId = (int) $request->input('batch_id');
$masaExpired = $this->normalizeDateInput($request->input('masa_expired'));
$alasan = trim((string) $request->input('alasan', 'Update expired barang terbuka'));
if ($batchId <= 0 || is_null($masaExpired)) {
return response()->json(['icon' => 'error', 'warna' => '#bf441d', 'status' => 'Gagal', 'message' => 'Batch dan tanggal expired baru wajib diisi']);
}
$row = SIMBHPReport::where('id', $batchId)
->whereNotNull('pemasukan')
->where('pemasukan', '>', 0)
->first();
if (!$row) {
return response()->json(['icon' => 'error', 'warna' => '#bf441d', 'status' => 'Gagal', 'message' => 'Batch barang masuk tidak ditemukan']);
}
$oldExpired = $row->masa_expired;
$keterangan = trim((string) ($row->keterangan ?? ''));
$suffix = 'Expired dibuka: '.($oldExpired ?: '-').' -> '.$masaExpired;
if ($alasan !== '') {
$suffix .= ' | '.$alasan;
}
$row->masa_expired = $masaExpired;
$row->keterangan = $keterangan !== '' ? $keterangan.' | '.$suffix : $suffix;
$row->updated_at = date("Y-m-d H:i:s");
$row->save();
return response()->json(['status' => 'Success', 'message' => 'Masa expired batch berhasil diupdate']);
}
public function storeRacikan(Request $request)
{
if (Session::get('previlage') == '') {
return response()->json(['message' => 'Unauthorized'], 401);
}
$tanggal = trim((string) $request->input('tanggal', date('Y-m-d')));
$namaRacikan = trim((string) $request->input('nama_racikan', 'Racikan'));
$statusRacikan = strtolower(trim((string) $request->input('status_racikan', 'berhasil')));
$bahan = $request->input('bahan', []);
if (is_string($bahan)) {
$decoded = json_decode($bahan, true);
$bahan = is_array($decoded) ? $decoded : [];
}
if ($tanggal === '') {
$tanggal = date('Y-m-d');
}
if ($namaRacikan === '') {
$namaRacikan = 'Racikan';
}
if (!in_array($statusRacikan, ['berhasil', 'rusak'], true)) {
return response()->json(['icon' => 'error', 'warna' => '#bf441d', 'status' => 'Gagal', 'message' => 'Status racikan tidak valid']);
}
if (!is_array($bahan) || count($bahan) === 0) {
return response()->json(['icon' => 'error', 'warna' => '#bf441d', 'status' => 'Gagal', 'message' => 'Minimal satu bahan racikan wajib diisi']);
}
$parts = explode('-', $tanggal);
$tahun = (int) ($parts[0] ?? date('Y'));
$wulan = (int) ($parts[1] ?? date('m'));
$dino = (int) ($parts[2] ?? date('d'));
$marking = 'RACIK-'.date('YmdHis').'-'.random_int(100, 999);
try {
DB::transaction(function () use ($request, $bahan, $statusRacikan, $namaRacikan, $marking, $dino, $wulan, $tahun) {
$service = app(SimbhpStockService::class);
$prepared = [];
$requiredByJenis = [];
foreach ($bahan as $line) {
$jenisNama = trim((string) ($line['jenis'] ?? ''));
$qty = (int) str_replace(',', '', (string) ($line['qty'] ?? 0));
$satuanTransaksi = (string) ($line['satuan_transaksi'] ?? 'besar');
if (!in_array($satuanTransaksi, ['besar', 'kecil'], true)) {
$satuanTransaksi = 'besar';
}
if ($jenisNama === '' || $qty <= 0) {
throw new \InvalidArgumentException('Bahan dan jumlah wajib diisi');
}
$jenis = SIMBHPJenis::where('jenis', $jenisNama)->first();
if (!$jenis) {
throw new \InvalidArgumentException('Bahan '.$jenisNama.' tidak ditemukan');
}
$setting = $service->getUnitSetting($jenis);
if ($satuanTransaksi === 'kecil' && !($setting['has_breakdown'] ?? false)) {
throw new \InvalidArgumentException('Satuan kecil tidak tersedia untuk '.$jenisNama);
}
$qtyBase = $service->calculateBaseQty($jenis, $qty, $satuanTransaksi);
$requiredByJenis[$jenisNama] = ($requiredByJenis[$jenisNama] ?? 0) + $qtyBase;
$prepared[] = [
'jenis' => $jenis,
'qty' => $qty,
'qty_base' => $qtyBase,
'satuan_transaksi' => $satuanTransaksi,
'batch_id' => (int) ($line['batch_id'] ?? 0),
'expired_baru' => $this->normalizeDateInput($line['expired_baru'] ?? null),
];
}
foreach ($requiredByJenis as $jenisNama => $requiredBase) {
$stok = $service->getStockBaseByJenis($jenisNama);
if ($requiredBase > $stok) {
throw new \InvalidArgumentException('Stok bahan '.$jenisNama.' tidak cukup');
}
}
foreach ($prepared as $line) {
$keterangan = $statusRacikan === 'berhasil' ? 'Bahan racikan berhasil' : 'Bahan racikan rusak';
if ($line['expired_baru'] && $line['batch_id'] > 0) {
$batch = SIMBHPReport::where('id', $line['batch_id'])
->where('jenis', $line['jenis']->jenis)
->whereNotNull('pemasukan')
->where('pemasukan', '>', 0)
->first();
if (!$batch) {
throw new \InvalidArgumentException('Batch expired untuk '.$line['jenis']->jenis.' tidak ditemukan');
}
$oldExpired = $batch->masa_expired;
$oldKeterangan = trim((string) ($batch->keterangan ?? ''));
$expiredNote = 'Expired dibuka racikan '.$marking.': '.($oldExpired ?: '-').' -> '.$line['expired_baru'];
$batch->masa_expired = $line['expired_baru'];
$batch->keterangan = $oldKeterangan !== '' ? $oldKeterangan.' | '.$expiredNote : $expiredNote;
$batch->updated_at = date("Y-m-d H:i:s");
$batch->save();
$keterangan .= ' | Expired dibuka: '.($oldExpired ?: '-').' -> '.$line['expired_baru'];
}
SIMBHPReport::create([
'tanggal' => $dino,
'bulan' => $wulan,
'tahun' => $tahun,
'deskripsi' => 'Racikan '.$namaRacikan.' - bahan',
'pemasukan' => null,
'pengeluaran' => $line['qty'],
'qty_base' => $line['qty_base'],
'satuan_transaksi' => $line['satuan_transaksi'],
'jenis' => $line['jenis']->jenis,
'keterangan' => $keterangan,
'marking' => $marking,
]);
}
if ($statusRacikan === 'berhasil') {
$hasil = $this->resolveRacikanOutputJenis($request);
$qtyHasil = (int) str_replace(',', '', (string) $request->input('hasil_qty', 0));
$satuanHasil = (string) $request->input('hasil_satuan_transaksi', 'besar');
if (!in_array($satuanHasil, ['besar', 'kecil'], true)) {
$satuanHasil = 'besar';
}
if ($qtyHasil <= 0) {
throw new \InvalidArgumentException('Jumlah hasil racikan wajib diisi');
}
$setting = $service->getUnitSetting($hasil);
if ($satuanHasil === 'kecil' && !($setting['has_breakdown'] ?? false)) {
throw new \InvalidArgumentException('Satuan kecil tidak tersedia untuk hasil racikan');
}
$qtyBaseHasil = $service->calculateBaseQty($hasil, $qtyHasil, $satuanHasil);
SIMBHPReport::create([
'tanggal' => $dino,
'bulan' => $wulan,
'tahun' => $tahun,
'deskripsi' => 'Hasil racikan '.$namaRacikan,
'pemasukan' => $qtyHasil,
'pengeluaran' => null,
'qty_base' => $qtyBaseHasil,
'satuan_transaksi' => $satuanHasil,
'masa_expired' => $this->normalizeDateInput($request->input('hasil_masa_expired')),
'jenis' => $hasil->jenis,
'keterangan' => 'Hasil racikan berhasil',
'marking' => $marking,
]);
}
});
} catch (\Throwable $e) {
return response()->json(['icon' => 'error', 'warna' => '#bf441d', 'status' => 'Gagal', 'message' => $e->getMessage()]);
}
$message = $statusRacikan === 'berhasil'
? 'Racikan berhasil disimpan dan hasil masuk stok'
: 'Racikan rusak disimpan sebagai pengurangan bahan';
return response()->json(['status' => 'Success', 'message' => $message]);
}
public function exAddbarang(Request $request) {
$deskripsi = $request->input('set01');
$pos = $request->input('set02');
@@ -919,6 +1151,82 @@ class GudangController extends Controller
return null;
}
private function resolveRacikanOutputJenis(Request $request): SIMBHPJenis
{
$mode = (string) $request->input('hasil_mode', 'existing');
if ($mode === 'new') {
$nama = trim((string) $request->input('hasil_new_jenis'));
$satuan = trim((string) $request->input('hasil_new_satuan'));
$kode = trim((string) $request->input('hasil_new_kode'));
$satuanKecil = trim((string) $request->input('hasil_new_satuan_kecil'));
$konversi = (int) $request->input('hasil_new_konversi', 1);
$stokMinimum = (int) $request->input('hasil_new_stok_minimum', 0);
if ($nama === '' || $satuan === '') {
throw new \InvalidArgumentException('Nama dan satuan hasil racikan baru wajib diisi');
}
if ($konversi <= 0) {
$konversi = 1;
}
if ($stokMinimum < 0) {
$stokMinimum = 0;
}
if ($satuanKecil !== '' && $konversi <= 1) {
throw new \InvalidArgumentException('Konversi hasil racikan baru harus lebih dari 1 jika memakai satuan kecil');
}
if ($kode !== '') {
$kode = strtoupper($kode);
$kode = preg_replace('/\s+/', '', $kode);
$kode = preg_replace('/[^A-Z0-9._-]/', '', $kode);
} else {
$kode = strtoupper($nama);
$kode = preg_replace('/\s+/', '', $kode);
$kode = preg_replace('/[^A-Z0-9._-]/', '', $kode);
}
if ($kode === '' || strlen($kode) < 2 || strlen($kode) > 40) {
throw new \InvalidArgumentException('Kode hasil racikan wajib 2-40 karakter');
}
$existing = SIMBHPJenis::where('kodejenis', $kode)->first();
if ($existing) {
return $existing;
}
$jenis = SIMBHPJenis::create([
'kodejenis' => $kode,
'jenis' => $nama,
'satuan' => $satuan,
'satuan_kecil' => $satuanKecil,
'konversi_kecil' => $konversi,
'stok_minimum' => $stokMinimum,
]);
if (Schema::hasColumn('simbhpjenis', 'barcode_besar')) {
$service = app(SimbhpStockService::class);
$setting = $service->getUnitSetting($jenis);
$jenis->update([
'barcode_besar' => $service->makeBarcodeValue((int) $jenis->id, 'besar'),
'barcode_kecil' => ($setting['has_breakdown'] ?? false) ? $service->makeBarcodeValue((int) $jenis->id, 'kecil') : null,
]);
}
return $jenis;
}
$jenisNama = trim((string) $request->input('hasil_jenis'));
if ($jenisNama === '') {
throw new \InvalidArgumentException('Barang hasil racikan wajib dipilih');
}
$jenis = SIMBHPJenis::where('jenis', $jenisNama)->first();
if (!$jenis) {
throw new \InvalidArgumentException('Barang hasil racikan tidak ditemukan');
}
return $jenis;
}
private function getJenisRows()
{
$rows = [];
+2 -26
View File
@@ -409,7 +409,8 @@ class ListController extends Controller
if ($jenis == 'ThisDay') {
$arraylist = $arraylist->where(function($q) {
$q->where(function($sub) {
$sub->whereNotIn('status', ['Selesai', 'Arsip'])
$sub->whereNotNull('status')
->whereNotIn('status', ['', 'Selesai', 'Arsip', 'Penerimaan Sampel'])
->where('status', 'not like', 'Dibatalkan%');
});
});
@@ -505,31 +506,6 @@ class ListController extends Controller
'orderid' => $getdata->orderid,
]);
try {
if ($getdata->kd_spesimen != '' AND $getdata->nm_spesimen != ''){
PendaftaranOnListiner::updateOrCreate(
[
'rnoreg' => $getdata->nofoto,
],
[
'rtglast' => date('Y-m-d'),
'norm' => $getdata->noregister,
'nama' => $getdata->nmpasien,
'alamat' => $getdata->alamatpasien,
'telp' => $getdata->tlppasien,
'hp' => $getdata->tlppasien,
'tgllahir' => $getdata->tgllahirpasien,
'umur' => $getdata->usia,
'rjenis' => $getdata->jkpasien,
'kodedok' => Session('id'),
'namadok' => Session('nama'),
'ruangan' => $getdata->ruangan,
'tes' => '',
'alat' => 'All',
'kd_spesimen' => $getdata->kd_spesimen,
'nm_spesimen' => $getdata->nm_spesimen,
]
);
}
return response()->json(['status' => 'Sukses', 'message' => 'Data Order Berhasil di Gandakan'], 201);
} catch (Exception $e) {
return response()->json(['status' => 'Sukses', 'message' => 'Data Order Berhasil di Gandakan'], 201);
File diff suppressed because it is too large Load diff
+23 -15
View File
@@ -36,11 +36,12 @@ class PoliController extends Controller
$subpoli = $request->input('subpoli');
$subsubpoli = $request->input('subsubpoli');
Poli::create([
'poli' => $request->input('poli'),
'subpoli' => $request->input('subpoli'),
'poli' => $request->input('poli'),
'subpoli' => $request->input('subpoli'),
'subsubpoli'=> $request->input('subsubpoli'),
'modaliti' => $request->input('modaliti'),
'modaliti2' => $request->input('modaliti2')
'modaliti' => $request->input('modaliti'),
'modaliti2' => $request->input('modaliti2'),
'kelompok' => $request->input('kelompok')
]);
Periksa::where('reques', $subpoli)->update([
'kd_spesimen' => $request->input('subsubpoli'),
@@ -58,25 +59,32 @@ class PoliController extends Controller
public function updatePoli(Request $request) {
$validator = Validator::make($request->all(), [
'id' => 'required',
'poli' => 'required',
'subpoli' => 'required',
'modaliti' => 'required'
'id' => 'required',
'poli' => 'required',
'subpoli' => 'required',
'modaliti' => 'required'
]);
if($validator->fails()) {
return response()->json(['status' => 'error', 'message' => 'Please fill input field or fill with right input']);
} else {
$id = $request->input('id');
$poli = $request->input('poli');
$subpoli = $request->input('subpoli');
$id = $request->input('id');
$poli = $request->input('poli');
$subpoli = $request->input('subpoli');
$subsubpoli = $request->input('subsubpoli');
$kelompok = $request->input('kelompok');
Poli::where('id', $id)->update([
'poli' => $poli,
'subpoli' => $subpoli,
'poli' => $poli,
'subpoli' => $subpoli,
'subsubpoli'=> $subsubpoli,
'modaliti' => $request->input('modaliti'),
'modaliti2' => $request->input('modaliti2')
'modaliti' => $request->input('modaliti'),
'modaliti2' => $request->input('modaliti2'),
'kelompok' => $request->input('kelompok')
]);
if ($kelompok != ''){
Periksa::where('reques', $subpoli)->whereNull('status')->update([
'dlp' => $kelompok
]);
}
Periksa::where('reques', $subpoli)->update([
'kd_spesimen' => $subsubpoli,
'nm_spesimen' => $request->input('modaliti'),
+155 -47
View File
@@ -340,6 +340,108 @@ class ReportController extends Controller
? 'MEMENUHI TARGET'
: 'TIDAK MEMENUHI TARGET';
}
private function normalizeBtaSpValue($value) {
$value = trim(strip_tags((string) $value));
$normalized = strtolower(str_replace(' ', '', $value));
if ($normalized === '+++' || $normalized === '3+') {
return '+++';
}
if ($normalized === '++' || $normalized === '2+') {
return '++';
}
if ($normalized === '+' || $normalized === '1+') {
return '+';
}
if (in_array($normalized, ['1-9', '1s/d9', '1sd9'], true)) {
return '1-9';
}
if (in_array($normalized, ['neg', 'negatif', 'negative'], true)) {
return 'Neg';
}
return null;
}
private function buildMikroskopisBtaSpReport($bulan, $tahun) {
$nilaiList = ['+++', '++', '+', '1-9', 'Neg'];
$report = [];
foreach (['L' => 'Laki-laki', 'P' => 'Perempuan'] as $kodeKelamin => $labelKelamin) {
foreach ($nilaiList as $nilai) {
$report[$kodeKelamin][$nilai] = [
'kelamin' => $labelKelamin,
'nilai' => $nilai,
'pagi' => 0,
'sewaktu' => 0,
];
}
}
$query = DB::table('db_komponenjawaban')
->select('db_komponenjawaban.komponen', 'db_komponenjawaban.isidata', 'periksa.jkpasien')
->leftJoin('periksa', 'db_komponenjawaban.accnumber', 'periksa.nofoto')
->where(function ($q) {
$q->where('db_komponenjawaban.komponen', 'LIKE', '%hasilpemeriksaantbbiakan_pagi%')
->orWhere('db_komponenjawaban.komponen', 'LIKE', '%hasilpemeriksaantbbiakan_sewaktu%');
})
->whereNotNull('db_komponenjawaban.isidata')
->where('db_komponenjawaban.isidata', '!=', '')
->whereYear('periksa.daftar', $tahun);
if ($bulan != 'ALL' && $bulan != 'Pick Month') {
$query->whereMonth('periksa.daftar', $bulan);
}
foreach ($query->get() as $row) {
$kelamin = $row->jkpasien == 'L' ? 'L' : 'P';
$nilai = $this->normalizeBtaSpValue($row->isidata);
if ($nilai === null || !isset($report[$kelamin][$nilai])) {
continue;
}
if (stripos($row->komponen, 'hasilpemeriksaantbbiakan_pagi') !== false) {
$report[$kelamin][$nilai]['pagi']++;
}
if (stripos($row->komponen, 'hasilpemeriksaantbbiakan_sewaktu') !== false) {
$report[$kelamin][$nilai]['sewaktu']++;
}
}
return array_merge(array_values($report['L']), array_values($report['P']));
}
public function genMikroskopisBtaSpReport(Request $request) {
$bulan = $request->input('bulan');
$tahun = $request->input('tahun');
if ($tahun == '' OR is_null($tahun)){
$getarray = explode('?', $bulan);
$bulan = $getarray[0] ?? date('m');
$tahun = $getarray[1] ?? date('Y');
$bulan = str_replace('bulan=', '', $bulan);
$tahun = str_replace('tahun=', '', $tahun);
}
$nilaiList = ['+++', '++', '+', '1-9', 'Neg'];
$reportRows = collect($this->buildMikroskopisBtaSpReport($bulan, $tahun));
$rekapLaki = $reportRows
->where('kelamin', 'Laki-laki')
->keyBy('nilai')
->toArray();
$rekapPerempuan = $reportRows
->where('kelamin', 'Perempuan')
->keyBy('nilai')
->toArray();
return view('cetak.mikroskopisbtasp', compact(
'rekapLaki',
'rekapPerempuan',
'nilaiList',
'bulan',
'tahun'
));
}
public function report(Request $request) {
$bulan = $request->input('bulan');
$tahun = $request->input('tahun');
@@ -355,6 +457,9 @@ class ReportController extends Controller
$order = $request->input('order') ?? 'id desc';
$filterscount = $request->input('filterscount') ?? 0;
$isMdrReport = $jenisreport == 'list_hasil_mdr';
if ($jenisreport == 'mikroskopis_bta_sp') {
return response()->json($this->buildMikroskopisBtaSpReport($bulan, $tahun));
}
if ($jenisreport == 'biorepository'){
if ($bulan == 'ALL'){
$queryBiorepo = DB::table('db_komponenjawaban')
@@ -448,6 +553,13 @@ class ReportController extends Controller
->whereIn('id', array_unique($poli_ids))
->get()
->keyBy('id');
$preliminaries = DB::table('riwayatjawaban')
->select('nofoto', 'created_at')
->whereIn('nofoto', array_unique($acc))
->where('keterangan', 'Preliminary Results')
->orderBy('created_at', 'DESC')
->get()
->groupBy('nofoto');
foreach ($hasil as $row) {
$k = $komponen[$row->nofoto] ?? collect([]);
$row->id_bakteri01 = optional($k->firstWhere('komponen','id_bakteri01'))->isidata;
@@ -462,6 +574,7 @@ class ReportController extends Controller
->implode('; ');
$row->filefoto = "<a href='".url('/hasil/'.$row->nofoto)."'>$row->nofoto</a>";
$row->tgl_preliminary = optional(($preliminaries[$row->nofoto] ?? collect([]))->first())->created_at;
$p = $poli[$row->poli_id] ?? null;
$row->subpoli = $p->subpoli ?? null;
$row->modaliti2 = $this->resolveTatTargetDays($p->modaliti2 ?? '1');
@@ -1136,7 +1249,6 @@ class ReportController extends Controller
}
public function genTATreport(Request $request) {
$data = [];
$bulan = $request->input('bulan');
$tahun = $request->input('tahun');
if ($tahun == '' OR is_null($tahun)){
@@ -1146,59 +1258,55 @@ class ReportController extends Controller
$bulan = str_replace('bulan=', '', $bulan);
$tahun = str_replace('tahun=', '', $tahun);
}
$query = Periksa::select('*');
if ($bulan !== 'ALL') {
$query->whereMonth('mulai', $bulan);
}
$periksa = $query->whereYear('mulai', $tahun)->orderBy('mulai', 'ASC')->get();
$startDate = $bulan === 'ALL'
? Carbon::createFromDate((int) $tahun, 1, 1)->startOfYear()
: Carbon::createFromDate((int) $tahun, (int) $bulan, 1)->startOfMonth();
$endDate = $bulan === 'ALL'
? Carbon::createFromDate((int) $tahun, 1, 1)->endOfYear()
: Carbon::createFromDate((int) $tahun, (int) $bulan, 1)->endOfMonth();
if ($periksa->count() == 0) {
$query = DB::table('periksa')
->leftJoin('poli', 'poli.id', '=', 'periksa.poli_id')
->select('periksa.id', 'periksa.kd_spesimen', 'periksa.mulai', 'periksa.verifikasi', 'poli.modaliti2')
->whereBetween('periksa.mulai', [$startDate->toDateTimeString(), $endDate->toDateTimeString()]);
$rekapRows = [];
$query->chunkById(1000, function ($rows) use (&$rekapRows) {
foreach ($rows as $row) {
$key = $row->kd_spesimen ?: '-';
if (!isset($rekapRows[$key])) {
$rekapRows[$key] = [
'jenis_pemeriksaan' => $key,
'memenuhi' => 0,
'tidak_memenuhi' => 0,
'tidak_ada_hasil' => 0,
'total' => 0,
];
}
if (empty($row->verifikasi)) {
$rekapRows[$key]['tidak_ada_hasil']++;
} else {
$target = $this->resolveTatTargetDays($row->modaliti2 ?? '1');
$status = $this->resolveTatStatus($row->mulai, $row->verifikasi, $target);
if ($status === 'MEMENUHI TARGET') {
$rekapRows[$key]['memenuhi']++;
} else {
$rekapRows[$key]['tidak_memenuhi']++;
}
}
$rekapRows[$key]['total']++;
}
}, 'periksa.id', 'id');
if (empty($rekapRows)) {
return response()->json([]);
}
$accnumbers = $periksa->pluck('nofoto')->toArray();
$komponen = DB::table('db_komponenjawaban')->whereIn('accnumber', $accnumbers)->whereIn('komponen', ['id_bakteri01','id_bakteri02','id_mikroorganisme'])->get()->groupBy('accnumber');
$poli = DB::table('poli')->whereIn('id', $periksa->pluck('poli_id'))->get()->keyBy('id');
$hasil = $periksa->map(function($row) use ($komponen, $poli){
$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->filefoto = "<a href='".url('/hasil/'.$row->nofoto)."'>$row->nofoto</a>";
$p = $poli[$row->poli_id] ?? null;
$row->subpoli = $p->subpoli ?? null;
$target = $p->modaliti2 ?? '1';
$row->modaliti2 = $target;
if (!empty($row->verifikasi)) {
$selisih_hari = Carbon::parse($row->mulai)->diffInDays(Carbon::parse($row->verifikasi));
$target = intval($target);
$hari = intval($selisih_hari);
$row->selisih_hari = $selisih_hari;
if ($hari <= $target) {
$row->status_target = "MEMENUHI TARGET";
} else {
$row->status_target = "TIDAK MEMENUHI TARGET";
}
} else {
$row->selisih_hari = null;
$row->status_target = "TIDAK ADA HASIL";
}
return $row;
});
$rekap = $hasil->groupBy('kd_spesimen')->map(function($group){
return [
'jenis_pemeriksaan' => $group->first()->kd_spesimen,
'memenuhi' => $group->where('status_target', 'MEMENUHI TARGET')->count(),
'tidak_memenuhi' => $group->where('status_target', 'TIDAK MEMENUHI TARGET')->count(),
'tidak_ada_hasil' => $group->where('status_target', 'TIDAK ADA HASIL')->count(),
'total' => $group->count()
];
})->values();
$rekap = collect(array_values($rekapRows));
return view('cetak.tatreport', [
'bulan' => $bulan,
'tahun' => $tahun,
'detail' => $hasil,
'rekap' => $rekap
]);
}
@@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
* @var array<int, string>
*/
protected $except = [
'registerpasien', 'registerjson', 'ekstrakhasil', 'prosesbatal', 'cekaksess', 'ekstrakexpertise', 'getnolabnorm'
'registerpasien', 'registerjson', 'ekstrakhasil', 'prosesbatal', 'cekaksess', 'ekstrakexpertise', 'getnolabnorm', 'registerulang'
];
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace App\Livewire;
use App\BioCabinet;
use App\BioRack;
use Livewire\Component;
class BiorepositoryVisual extends Component
{
public ?int $selectedCabinetId = null;
public ?int $selectedRackId = null;
public ?int $selectedSlot = null;
public ?int $selectedShelf = null;
public ?int $selectedBox = null;
public function mount(): void
{
$this->selectedCabinetId = BioCabinet::orderBy('name', 'ASC')->value('id');
}
public function selectCabinet(int $cabinetId): void
{
$this->selectedCabinetId = $cabinetId;
$this->selectedRackId = null;
$this->resetPosition();
}
public function selectRack(int $rackId): void
{
$this->selectedRackId = $rackId;
$this->resetPosition();
}
public function selectSlot(int $slot): void
{
$this->selectedSlot = $slot;
$this->selectedShelf = null;
$this->selectedBox = null;
}
public function selectShelf(int $shelf): void
{
$this->selectedShelf = $shelf;
$this->selectedBox = null;
}
public function selectBox(int $box): void
{
$this->selectedBox = $box;
}
public function render()
{
$cabinets = BioCabinet::withCount(['racks', 'specimens'])
->orderBy('name', 'ASC')
->get();
if (!$this->selectedCabinetId && $cabinets->isNotEmpty()) {
$this->selectedCabinetId = (int) $cabinets->first()->id;
}
$selectedCabinet = $this->selectedCabinetId
? BioCabinet::with([
'racks' => function ($query) {
$query->withCount('specimens')
->orderBy('rack_number', 'ASC')
->orderBy('code', 'ASC');
},
])->find($this->selectedCabinetId)
: null;
$selectedRack = $this->selectedRackId
? BioRack::with(['specimens' => function ($query) {
$query->orderBy('slot_number', 'ASC')
->orderBy('shelf_number', 'ASC')
->orderBy('box_number', 'ASC')
->orderBy('tube_number', 'ASC');
}])->find($this->selectedRackId)
: null;
if ($selectedRack && (int) $selectedRack->cabinet_id !== (int) $this->selectedCabinetId) {
$selectedRack = null;
$this->selectedRackId = null;
$this->resetPosition();
}
return view('livewire.biorepository-visual', [
'cabinets' => $cabinets,
'selectedCabinet' => $selectedCabinet,
'selectedRack' => $selectedRack,
]);
}
private function resetPosition(): void
{
$this->selectedSlot = null;
$this->selectedShelf = null;
$this->selectedBox = null;
}
}
+6 -2
View File
@@ -8,10 +8,14 @@ class Poli extends Model
{
protected $table = "poli";
protected $fillable = [
'id',
'poli',
'subpoli',
'subsubpoli',
'modaliti',
'modaliti2',
'modaliti',
'kelompok',
'modaliti2',
'created_at',
'updated_at',
];
}
+221 -41
View File
@@ -64,6 +64,147 @@ class AstmMessageService
return preg_replace('/[^\x20-\x7E|]+/', '', $string);
}
protected function updatePeriksaStatusByHierarchy($nofoto, $proposedStatus): void
{
$proposedStatus = trim((string) $proposedStatus);
if ($nofoto === null || $nofoto === '' || $proposedStatus === '') {
return;
}
Periksa::where('nofoto', $nofoto)->whereNotNull('status')->get()->each(function ($periksa) use ($proposedStatus) {
if (in_array($periksa->status, ['Arsip', 'Dibatalkan (Arsip)', 'Batal', 'Selesai'], true)) {
return;
}
$resolvedStatus = $this->resolvePeriksaHierarchyStatus($periksa->status, $proposedStatus);
if ($resolvedStatus !== $periksa->status) {
$periksa->update(['status' => $resolvedStatus]);
}
});
}
protected function resolvePeriksaHierarchyStatus($currentStatus, $proposedStatus): string
{
$currentStatus = trim((string) $currentStatus);
$proposedStatus = trim((string) $proposedStatus);
if ($proposedStatus === '') {
return $currentStatus;
}
$currentRank = $this->periksaHierarchyStatusRank($currentStatus);
$proposedRank = $this->periksaHierarchyStatusRank($proposedStatus);
if ($currentStatus === '') {
return $proposedStatus;
}
if ($proposedRank === null) {
return $currentRank === null ? $proposedStatus : $currentStatus;
}
if ($currentRank === null || $proposedRank >= $currentRank) {
return $proposedStatus;
}
return $currentStatus;
}
protected function periksaHierarchyStatusRank($status): ?int
{
$normalized = $this->normalizePeriksaStatus($status);
if ($normalized === '') {
return null;
}
if ($normalized === 'penerimaan sampel') {
return 1;
}
if (in_array($normalized, ['pemeriksaan awal', 'pemeriksaan sampel', 'proses analisis sampel'], true)) {
return 2;
}
if (str_starts_with($normalized, 'data bd di terima') || str_starts_with($normalized, 'data bd diterima')) {
return 3;
}
if ($normalized === 'pewarnaan langsung') {
return 4;
}
if ($this->isProsesingKoloniMediaStatus($normalized)) {
return 5;
}
if (str_starts_with($normalized, 'data malditof diterima')
|| str_starts_with($normalized, 'data malditof di terima')
|| str_starts_with($normalized, 'data genexpert diterima')
|| str_starts_with($normalized, 'data genexpert di terima')) {
return 6;
}
if (str_starts_with($normalized, 'otor maldi (un verified)')) {
return 7;
}
if (str_starts_with($normalized, 'preliminary results')) {
return 8;
}
if (str_starts_with($normalized, 'data vitek di terima') || str_starts_with($normalized, 'data vitek diterima')) {
return 9;
}
if (str_starts_with($normalized, 'id/ast pending result')) {
return 10;
}
if (in_array($normalized, ['expertise', 'selesai', 'final result'], true) || str_starts_with($normalized, 'expertise saved (un verified)')) {
return 11;
}
return null;
}
protected function normalizePeriksaStatus($status): string
{
$status = strtolower(trim((string) $status));
$status = str_replace(['_', '-'], ' ', $status);
$status = preg_replace('/\s+/', ' ', $status);
return trim($status);
}
protected function isProsesingKoloniMediaStatus(string $normalizedStatus): bool
{
$exactStatuses = [
'tidak ada pertumbuhan',
'tidak lanjut identifikasi',
'tidak tindak lanjut identifikasi',
'inkubasi lanjutan',
'sub kultur',
'menunggu kultur yg lain',
'menunggu kultur yang lain',
'proses identifikasi dan uji kepekaan',
'sedang malditof',
'sedang id+ast',
'pertumbuhan primer',
];
if (in_array($normalizedStatus, $exactStatuses, true)) {
return true;
}
return str_starts_with($normalizedStatus, 'prosesing koloni media')
|| str_starts_with($normalizedStatus, 'processing koloni media')
|| str_starts_with($normalizedStatus, 'proses identifikasi')
|| str_starts_with($normalizedStatus, 'proses uji kepekaan');
}
/**
* Parsing data berdasarkan format yang diberikan
*/
@@ -240,6 +381,51 @@ class AstmMessageService
return explode('^', $value)[0] ?: null;
}
protected function normalizeAstmRecordLines(string $raw): string
{
$normalized = str_replace(["\r\n", "\r"], "\n", $raw);
if (str_contains($normalized, "\n")) {
return $normalized;
}
$fields = explode('|', $normalized);
$records = [];
$current = [];
foreach ($fields as $index => $field) {
$isSegmentStart = $this->isAstmSegmentStart($fields, $index);
if ($isSegmentStart && !empty($current)) {
$records[] = implode('|', $current);
$current = [];
}
$current[] = $field;
}
if (!empty($current)) {
$records[] = implode('|', $current);
}
return !empty($records) ? implode("\n", $records) : $normalized;
}
protected function isAstmSegmentStart(array $fields, int $index): bool
{
$field = $fields[$index] ?? '';
if ($field === 'H') {
return $index === 0;
}
if (!in_array($field, ['P', 'O', 'R', 'L'], true)) {
return false;
}
$next = $fields[$index + 1] ?? null;
return $next !== null && preg_match('/^\d+$/', (string) $next) === 1;
}
protected function processBDAstmResponse(string $raw, $data): bool
{
try {
@@ -382,9 +568,7 @@ class AstmMessageService
]
);
}
//Log::info("Data BD ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray());
$this->updatePeriksaStatusByHierarchy($accnumber, 'Data BD Diterima ('. $bdResult . ')');
return true;
} catch (\Throwable $e) {
@@ -576,9 +760,7 @@ class AstmMessageService
} else {
$status = 'Data Genexpert di Terima';
}
Periksa::where('nofoto', $accessionNumber)->whereNotIn('status', ['Selesai', 'Arsip', 'Dibatalkan (Arsip)', 'Batal'])->update([
'status' => $status,
]);
$this->updatePeriksaStatusByHierarchy($accessionNumber, $status);
}
Riwayat::create([
@@ -602,7 +784,7 @@ class AstmMessageService
}
public function processAstmMessagesgenExpert($response, $alat)
{
$normalizedData = str_replace(["\r\n", "\r"], "\n", $response);
$normalizedData = $this->normalizeAstmRecordLines($response);
$segments = explode("\n", $normalizedData);
$patient_id = '';
@@ -621,9 +803,12 @@ class AstmMessageService
// 1. Parsing Pasien
if ($recordType === 'P') {
$patient_id = !empty($cekdata[3]) ? $cekdata[3] : (!empty($cekdata[4]) ? $cekdata[4] : '');
$rawPatientId = $cekdata[2] ?? ($cekdata[3] ?? '');
$patient_id = trim(str_replace(['\R\\', '\\R\\', '\\'], '', $rawPatientId));
$raw_name = isset($cekdata[5]) ? trim(str_replace('^', ' ', $cekdata[5])) : '';
$raw_name = !empty($cekdata[4])
? trim(str_replace('^', ' ', $cekdata[4]))
: (isset($cekdata[5]) ? trim(str_replace('^', ' ', $cekdata[5])) : '');
$patient_name = !empty(trim($raw_name)) ? $raw_name : $patient_id;
}
@@ -650,24 +835,28 @@ class AstmMessageService
return trim($val) !== '';
}));
$target_name = '';
$metric_type = 'Result';
$target_parts = $target_match;
$last_part = end($target_parts);
// Jika string test_info mengandung 'Ct' atau 'EndPt', elemen terakhir adalah metrik,
// dan elemen sebelum terakhir adalah nama targetnya.
if (strpos($rsegmen, 'Ct|') !== false) {
// Jika string test_info mengandung metrik, elemen terakhir adalah metrik,
// dan elemen sebelumnya adalah nama target.
if ($last_part === 'Ct') {
$metric_type = 'Ct';
// Mengambil nama target (elemen sebelum 'Ct')
$target_name = count($target_match) > 1 ? $target_match[count($target_match) - 2] : '';
} elseif (strpos($rsegmen, 'EndPt|') !== false) {
array_pop($target_parts);
} elseif ($last_part === 'EndPt') {
$metric_type = 'EndPt';
// Mengambil nama target (elemen sebelum 'EndPt')
$target_name = count($target_match) > 1 ? $target_match[count($target_match) - 2] : '';
} else {
// Jika bukan Ct/EndPt, ini adalah Result murni.
// Target name biasanya adalah elemen terakhir dari array yang tidak kosong.
$target_name = count($target_match) > 0 ? end($target_match) : 'Unknown Target';
array_pop($target_parts);
} elseif (in_array($last_part, ['Delta Ct', 'LOG'], true)) {
$metric_type = $last_part;
array_pop($target_parts);
}
if (!empty($target_parts) && preg_match('/^\d+$/', (string) end($target_parts))) {
array_pop($target_parts);
}
$target_name = count($target_parts) > 0 ? end($target_parts) : 'Unknown Target';
$target_name = trim($target_name);
@@ -829,11 +1018,7 @@ class AstmMessageService
// ====================================================================
try {
Periksa::where('nofoto', $accession_number)
->whereNotIn('status', ['Selesai', 'Arsip', 'Dibatalkan (Arsip)', 'Batal'])
->update([
'status' => 'Data GeneXpert di Terima'
]);
$this->updatePeriksaStatusByHierarchy($accession_number, 'Data GeneXpert di Terima');
Riwayat::create([
'nofoto' => $accession_number,
@@ -861,11 +1046,14 @@ class AstmMessageService
}
public function processAstmResponse($response, $alat) {
// Bersihkan data
$astmData = $this->cleanString($response);
$astmData = $this->normalizeAstmRecordLines($this->cleanString($response));
// Validasi awal: response tidak kosong
if (empty($astmData)) {
Log::error("ASTM data kosong atau tidak valid.");
}
if (stripos($astmData, 'GeneXpert') !== false) {
return $this->processAstmMessagesgenExpert($astmData, $alat);
}
$jsonantibiotik = array(
'Oxacillin-OX',
'Cefoxitin-FOX',
@@ -995,9 +1183,7 @@ class AstmMessageService
// Simpan ke database
if ($noregister){
Periksa::where('nofoto', $accession_number)->update([
'status' => 'Data Vitek di Terima',
]);
$this->updatePeriksaStatusByHierarchy($accession_number, 'Data Vitek di Terima');
//Log::info("Data berhasil disimpan:", $resultSample->toArray());
$resultSample->save();
}
@@ -1176,9 +1362,7 @@ class AstmMessageService
}
}
Periksa::where('nofoto', $accession_number)->whereNotIn('status', ['Selesai', 'Arsip', 'Dibatalkan (Arsip)', 'Batal'])->update([
'status' => 'Data Vitek di Terima',
]);
$this->updatePeriksaStatusByHierarchy($accession_number, 'Data Vitek di Terima');
}
//Log::info("Data MTRL Berhasil di Parse dan di simpan ", $resultSample->toArray());
return response()->json(['message' => 'Data berhasil diproses dan disimpan.']);
@@ -1351,7 +1535,7 @@ class AstmMessageService
$resultSample->additional_result = json_encode($resultData);
if ($noregister){
$resultSample->save();
//Log::info("Data ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray());
$this->updatePeriksaStatusByHierarchy($accnumber, 'Data BD di Terima ('.$orderData[6] ?? ''.')');
return response()->json(['message' => 'Data berhasil diproses dan disimpan.']);
}
} else {
@@ -1373,9 +1557,7 @@ class AstmMessageService
DB::table('lis_phoenix')->where('id', $data->id)->update([
'processed' => $ok ? 1 : 9
]);
Periksa::where('nofoto', $data->no_id)->whereNotIn('status', ['Selesai', 'Arsip', 'Dibatalkan (Arsip)', 'Batal'])->update([
'status' => 'Data BD di Terima ('.$data->organisme.')',
]);
$this->updatePeriksaStatusByHierarchy($data->no_id, 'Data BD di Terima ('.$data->organisme.')');
KomponenJawaban::updateOrCreate(
[
'accnumber' => $data->no_id,
@@ -1445,9 +1627,7 @@ class AstmMessageService
DataListiner::where('urut', $data->urut)->update([
'processed' => $ok ? 1 : 9
]);
Periksa::where('nofoto', $data->no_id)->whereNotIn('status', ['Selesai', 'Arsip', 'Dibatalkan (Arsip)', 'Batal'])->update([
'status' => 'Data BD di Terima ('.$data->organisme.')',
]);
$this->updatePeriksaStatusByHierarchy($data->no_id, 'Data BD di Terima ('.$data->organisme.')');
KomponenJawaban::updateOrCreate(
[
'accnumber' => $data->no_id,
+16
View File
@@ -0,0 +1,16 @@
<?php
return [
'api_url' => env('CLOUD_BACKUP_API_URL', 'https://duidev.com'),
'api_token' => env('CLOUD_BACKUP_API_TOKEN'),
'backup_name' => env('CLOUD_BACKUP_NAME', 'lismikro'),
'chunk_size' => (int) env('CLOUD_BACKUP_CHUNK_SIZE', 5 * 1024 * 1024),
'download_path' => env('CLOUD_BACKUP_DOWNLOAD_PATH', storage_path('app/cloud-backups/lismikro_latest.backup')),
'pg_restore' => env('CLOUD_BACKUP_PG_RESTORE', 'pg_restore'),
'db_host' => env('CLOUD_BACKUP_DB_HOST', '10.10.123.193'),
'db_port' => env('CLOUD_BACKUP_DB_PORT', '5002'),
'db_database' => env('CLOUD_BACKUP_DB_DATABASE', 'lismikro'),
'db_username' => env('CLOUD_BACKUP_DB_USERNAME', 'lismikro'),
'db_password' => env('CLOUD_BACKUP_DB_PASSWORD'),
];
@@ -17,6 +17,7 @@ return new class extends Migration
$table->string('subpoli', 250)->nullable();
$table->string('subsubpoli', 250)->nullable();
$table->string('modaliti', 150)->nullable();
$table->string('kelompok', 150)->nullable();
$table->string('modaliti2', 150)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('bio_racks', function (Blueprint $table) {
if (!Schema::hasColumn('bio_racks', 'slot_count')) {
$table->integer('slot_count')->default(1)->after('name');
}
});
DB::table('bio_racks')
->where(function ($query) {
$query->whereNull('slot_count')
->orWhere('slot_count', '<=', 1);
})
->orderBy('id')
->get(['id', 'level'])
->each(function ($rack) {
DB::table('bio_racks')
->where('id', $rack->id)
->update(['slot_count' => max((int) ($rack->level ?? 1), 1)]);
});
}
public function down(): void
{
Schema::table('bio_racks', function (Blueprint $table) {
if (Schema::hasColumn('bio_racks', 'slot_count')) {
$table->dropColumn('slot_count');
}
});
}
};
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('poli', function (Blueprint $table) {
if (!Schema::hasColumn('poli', 'kelompok')) {
$table->string('kelompok', 150)->nullable();
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('poli', function (Blueprint $table) {
if (Schema::hasColumn('poli', 'kelompok')) {
$table->dropColumn('kelompok');
}
});
}
};
@@ -0,0 +1,72 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (!Schema::hasTable('poli')) {
Schema::create('poli', function (Blueprint $table) {
$table->id();
$table->string('poli', 250)->nullable();
$table->string('subpoli', 250)->nullable();
$table->string('subsubpoli', 250)->nullable();
$table->string('modaliti', 150)->nullable();
$table->string('kelompok', 150)->nullable();
$table->string('modaliti2', 150)->nullable();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
});
return;
}
Schema::table('poli', function (Blueprint $table) {
if (!Schema::hasColumn('poli', 'poli')) {
$table->string('poli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'subpoli')) {
$table->string('subpoli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'subsubpoli')) {
$table->string('subsubpoli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'modaliti')) {
$table->string('modaliti', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'kelompok')) {
$table->string('kelompok', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'modaliti2')) {
$table->string('modaliti2', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'created_at')) {
$table->timestamp('created_at')->nullable();
}
if (!Schema::hasColumn('poli', 'updated_at')) {
$table->timestamp('updated_at')->nullable();
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Kolom poli dipakai data production, rollback tidak menghapus kolom.
}
};
+101 -8
View File
@@ -2,10 +2,11 @@
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use App\Poli;
use Illuminate\Support\Facades\Schema;
class PoliSeeder extends Seeder
{
/**
@@ -13,15 +14,107 @@ class PoliSeeder extends Seeder
*/
public function run(): void
{
Poli::truncate();
$json1 = file_get_contents(database_path('seeders/data/poli.json'));
$data1 = json_decode($json1, true);
$this->ensureTableAndColumns();
// Bagi data ke dalam batch 500
$chunks = array_chunk($data1, 500);
DB::table('poli')->truncate();
foreach ($chunks as $chunk) {
foreach (array_chunk($this->jsonData(), 500) as $chunk) {
DB::table('poli')->insert($chunk);
}
$this->resetAutoIncrement();
}
private function ensureTableAndColumns(): void
{
if (!Schema::hasTable('poli')) {
Schema::create('poli', function (Blueprint $table) {
$table->id();
$table->string('poli', 250)->nullable();
$table->string('subpoli', 250)->nullable();
$table->string('subsubpoli', 250)->nullable();
$table->string('modaliti', 150)->nullable();
$table->string('kelompok', 150)->nullable();
$table->string('modaliti2', 150)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
});
return;
}
Schema::table('poli', function (Blueprint $table) {
if (!Schema::hasColumn('poli', 'poli')) {
$table->string('poli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'subpoli')) {
$table->string('subpoli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'subsubpoli')) {
$table->string('subsubpoli', 250)->nullable();
}
if (!Schema::hasColumn('poli', 'modaliti')) {
$table->string('modaliti', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'kelompok')) {
$table->string('kelompok', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'modaliti2')) {
$table->string('modaliti2', 150)->nullable();
}
if (!Schema::hasColumn('poli', 'created_at')) {
$table->timestamp('created_at')->useCurrent();
}
if (!Schema::hasColumn('poli', 'updated_at')) {
$table->timestamp('updated_at')->useCurrent();
}
});
}
private function jsonData(): array
{
$path = database_path('seeders/data/poli.json');
$json = file_get_contents($path);
if ($json === false) {
throw new \RuntimeException("Gagal membuka file seeder poli: {$path}");
}
$data = json_decode($json, true);
if (!is_array($data)) {
throw new \RuntimeException("Format JSON seeder poli tidak valid: {$path}");
}
return $data;
}
private function resetAutoIncrement(): void
{
$driver = DB::getDriverName();
if ($driver === 'pgsql') {
DB::statement("
SELECT setval(
pg_get_serial_sequence('poli', 'id'),
(SELECT COALESCE(MAX(id), 1) FROM poli),
(SELECT COUNT(*) > 0 FROM poli)
)
");
return;
}
if ($driver === 'mysql') {
$nextId = ((int) DB::table('poli')->max('id')) + 1;
DB::statement("ALTER TABLE poli AUTO_INCREMENT = {$nextId}");
}
}
}
File diff suppressed because it is too large Load diff
Vendored Regular → Executable
View File
Regular → Executable
View File
View File
Regular → Executable
+87 -104
View File
@@ -158,7 +158,7 @@ File: Main Css File
}
#topnav .navbar-custom {
background-color: #FEF0E1;
background-color: #02c0ce;
}
#topnav .navbar-toggle {
@@ -174,7 +174,7 @@ File: Main Css File
}
#topnav .navbar-toggle:hover span {
background-color: #FEF0E1;
background-color: #313a46;
}
#topnav .navbar-toggle:focus {
@@ -182,7 +182,7 @@ File: Main Css File
}
#topnav .navbar-toggle:focus span {
background-color: #FEF0E1;
background-color: #313a46;
}
#topnav .navbar-toggle .lines {
@@ -240,72 +240,40 @@ File: Main Css File
}
#topnav .navigation-menu > li > a {
display: flex; /* Menggunakan Flexbox untuk alignment */
align-items: center; /* Membuat item (ikon + teks) rata tengah secara vertikal */
justify-content: center; /* Membuat item (ikon + teks) rata tengah secara horizontal */
color: #2F2F2F;
display: block;
color: rgba(255, 255, 255, 0.7);
font-weight: 500;
font-size: 16px;
-webkit-transition: all .3s ease;
transition: all .3s ease;
-webkit-transition: all .5s ease;
transition: all .5s ease;
line-height: 20px;
padding: 20px 25px;
border-radius: 10px;
min-width: 130px; /* Atur lebar minimum untuk semua menu */
padding-left: 25px;
padding-right: 25px;
}
#topnav .navigation-menu > li > a:hover {
color: #ffffff;
}
#topnav .navigation-menu > li > a:focus {
color: #ffffff;
}
#topnav .navigation-menu > li > a:active {
color: #ffffff;
}
#topnav .navigation-menu > li > a i {
margin-right: 8px; /* Memberi jarak antara ikon dan teks */
}
#topnav .navigation-menu > li > a:hover,
#topnav .navigation-menu > li.active > a {
background-color: #FFA14F; /* Warna menu di sorot dan aktif */
color: #2F2F2F; /* Warna font menu di sorot dan aktif */
}
/* =========== Submenu =============*/
#topnav .navigation-menu > li .submenu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
border: 1px solid #e7e7e7;
padding: 15px 0;
list-style: none;
min-width: 200px;
visibility: hidden;
opacity: 0;
margin-top: 10px;
-webkit-transition: all .2s ease;
transition: all .2s ease;
background-color: #FEF0E1; /* Background submenu */
box-shadow: 0 0 24px 0 rgba(0, 0, 0, 0.06), 0 1px 0 0 rgba(0, 0, 0, 0.02);
text-align: left;
}
#topnav .navigation-menu > li:hover > .submenu {
visibility: visible;
opacity: 1;
margin-top: 0;
}
#topnav .navigation-menu .submenu li a {
display: block;
padding: 8px 25px;
clear: both;
font-weight: normal;
white-space: nowrap;
font-size: 15px;
color: #2F2F2F;
-webkit-transition: all .35s ease;
transition: all .35s ease;
margin-right: 8px;
-webkit-transition: all .5s ease;
transition: all .5s ease;
}
/* ATURAN HOVER YANG DIPERBAIKI */
#topnav .navigation-menu .submenu li a:hover,
#topnav .navigation-menu .submenu li a:focus,
#topnav .navigation-menu .submenu li a:active {
background-color: #FFA14F; /* Warna hover submenu */
color: #2F2F2F;
#topnav .navigation-menu > li > a:hover, #topnav .navigation-menu > li > a:focus {
background-color: transparent;
}
/*
Responsive Menu
*/
@@ -328,16 +296,33 @@ File: Main Css File
margin-right: 10px;
}
#topnav .navigation-menu > li:hover a {
color: #2F2F2F !important;
color: #ffffff;
}
#topnav .navigation-menu > li:hover a i {
color: #2F2F2F !important;
color: #ffffff;
}
#topnav .navigation-menu > li > ul > li.has-submenu:active > a,
#topnav .navigation-menu > li > ul > li.has-submenu:hover > a {
color: #2F2F2F !important;
color: #02c0ce;
}
#topnav .navigation-menu > li .submenu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
border: 1px solid #e7e7e7;
padding: 15px 0;
list-style: none;
min-width: 200px;
text-align: left;
visibility: hidden;
opacity: 0;
margin-top: 10px;
-webkit-transition: all .2s ease;
transition: all .2s ease;
background-color: #ffffff;
box-shadow: 0 0 24px 0 rgba(0, 0, 0, 0.06), 0 1px 0 0 rgba(0, 0, 0, 0.02);
}
#topnav .navigation-menu > li .submenu.megamenu {
white-space: nowrap;
width: auto;
@@ -393,7 +378,7 @@ File: Main Css File
text-transform: uppercase;
letter-spacing: 2px;
font-weight: 500;
background-color: #FEF0E1; /* Background submenu */
color: #313a46;
}
#topnav .navbar-toggle {
display: none;
@@ -413,11 +398,10 @@ File: Main Css File
#topnav .navigation-menu {
float: none;
max-height: 400px;
text-align: center;
text-align: left;
}
#topnav .navigation-menu > li {
display: block;
text-align: center;
}
#topnav .navigation-menu > li > a {
color: #313a46;
@@ -446,8 +430,7 @@ File: Main Css File
color: #313a46;
}
#topnav .navigation-menu > li .submenu li a:hover {
background-color: #FFA14F;
color: #313a46;
color: #02c0ce;
}
#topnav .navigation-menu > li .submenu li.has-submenu > a:after {
content: "\F140";
@@ -502,10 +485,10 @@ File: Main Css File
overflow-y: auto;
}
#topnav .has-submenu.active a {
color: #FFA14F;
color: #02c0ce;
}
#topnav .has-submenu.active a:active, #topnav .has-submenu.active a:focus, #topnav .has-submenu.active a i {
color: #FFA14F;
color: #02c0ce;
}
}
@@ -1131,11 +1114,11 @@ code {
}
.bg-primary {
background-color: #FFA14F !important;
background-color: #2d7bf4 !important;
}
.bg-success {
background-color: #77C470 !important;
background-color: #0acf97 !important;
}
.bg-info {
@@ -1143,11 +1126,11 @@ code {
}
.bg-warning {
background-color: #FAA645 !important;
background-color: #f9bc0b !important;
}
.bg-danger {
background-color: #E8563C !important;
background-color: #f1556c !important;
}
.bg-muted {
@@ -1196,7 +1179,7 @@ code {
}
.text-warning {
color: #FAA645 !important;
color: #f9bc0b !important;
}
.text-success {
@@ -1233,7 +1216,7 @@ code {
}
.border-warning {
border-color: #FAA645 !important;
border-color: #f9bc0b !important;
}
.border-danger {
@@ -1265,12 +1248,12 @@ code {
}
.badge-custom {
background: #FFA14F;
background: #02c0ce;
color: #ffffff;
}
.badge-primary {
background-color: #FFA14F;
background-color: #2d7bf4;
}
.badge-secondary {
@@ -1286,7 +1269,7 @@ code {
}
.badge-warning {
background-color: #FAA645;
background-color: #f9bc0b;
color: #ffffff;
}
@@ -1679,15 +1662,15 @@ code {
}
.btn-warning {
background-color: #FAA645;
border: 1px solid #FAA645;
background-color: #f9bc0b;
border: 1px solid #f9bc0b;
}
.btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .btn-warning.focus, .btn-warning:active,
.btn-warning:focus, .btn-warning:hover, .open > .dropdown-toggle.btn-warning, .btn-warning:not([disabled]):not(.disabled).active,
.btn-warning:not([disabled]):not(.disabled):active, .show > .btn-warning.dropdown-toggle {
background-color: #FAA645;
border-color: #FAA645;
background-color: #e5ac06;
border-color: #e5ac06;
}
.btn-warning.focus, .btn-warning:focus, .btn-outline-warning.focus, .btn-outline-warning:focus,
@@ -1802,13 +1785,13 @@ code {
}
.btn-outline-warning {
color: #FAA645;
border-color: #FAA645;
color: #f9bc0b;
border-color: #f9bc0b;
}
.btn-outline-warning:hover {
background-color: #FAA645;
border-color: #FAA645;
background-color: #f9bc0b;
border-color: #f9bc0b;
}
.btn-outline-danger {
@@ -2000,7 +1983,7 @@ code {
}
.ribbon-box .ribbon-warning {
background: #FAA645;
background: #f9bc0b;
}
.ribbon-box .ribbon-warning:before {
@@ -2143,7 +2126,7 @@ code {
}
.ribbon-box .ribbon-two-warning span {
background: #FAA645;
background: #f9bc0b;
}
.ribbon-box .ribbon-two-warning span:before {
@@ -2426,8 +2409,8 @@ code {
}
.checkbox-warning input[type="checkbox"]:checked + label::before {
background-color: #FAA645;
border-color: #FAA645;
background-color: #f9bc0b;
border-color: #f9bc0b;
}
.checkbox-warning input[type="checkbox"]:checked + label::after {
@@ -2601,15 +2584,15 @@ code {
}
.radio-warning input[type="radio"] + label::after {
background-color: #FAA645;
background-color: #f9bc0b;
}
.radio-warning input[type="radio"]:checked + label::before {
border-color: #FAA645;
border-color: #f9bc0b;
}
.radio-warning input[type="radio"]:checked + label::after {
background-color: #FAA645;
background-color: #f9bc0b;
}
.radio-success input[type="radio"] + label::after {
@@ -2682,7 +2665,7 @@ code {
}
.alert-warning {
color: #FAA645;
color: #f9bc0b;
background-color: #fffaeb;
border-color: #fbd76f;
}
@@ -2741,9 +2724,9 @@ code {
}
.jq-icon-warning {
background-color: #FAA645;
background-color: #f9bc0b;
color: #ffffff;
border-color: #FAA645;
border-color: #f9bc0b;
}
.jq-icon-error {
@@ -3146,7 +3129,7 @@ code {
}
.progress-bar-warning {
background-color: #FAA645;
background-color: #f9bc0b;
}
.progress-bar-danger {
@@ -3280,8 +3263,8 @@ code {
}
.swal2-icon.swal2-warning {
color: #FAA645;
border-color: #FAA645;
color: #f9bc0b;
border-color: #f9bc0b;
}
.swal2-icon.swal2-error {
@@ -3397,7 +3380,7 @@ code {
}
.ct-chart .ct-series.ct-series-g .ct-bar, .ct-chart .ct-series.ct-series-g .ct-line, .ct-chart .ct-series.ct-series-g .ct-point, .ct-chart .ct-series.ct-series-g .ct-slice-donut {
stroke: #FAA645;
stroke: #f9bc0b;
}
.ct-series-a .ct-area, .ct-series-a .ct-slice-pie {
@@ -3590,7 +3573,7 @@ code {
}
.message-list li.orange-dot .col-mail-1 .dot {
border-color: #FAA645;
border-color: #f9bc0b;
}
.message-list li.green-dot .col-mail-1 .dot {
View File
@@ -17,6 +17,72 @@
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
}
.bio-step-panel {
border: 1px solid #d7e3ef;
border-radius: 8px;
background: #ffffff;
padding: 12px;
margin-bottom: 14px;
}
.bio-step-title {
font-weight: 700;
color: #1b3a57;
margin-bottom: 10px;
}
.bio-choice-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 10px;
}
.bio-choice-wrap {
position: relative;
}
.bio-choice {
display: block;
width: 100%;
min-height: 92px;
border: 1px solid #d7e3ef;
border-radius: 6px;
background: #f8fbff;
color: #1b3a57;
text-align: left;
padding: 10px;
cursor: pointer;
}
.bio-choice strong,
.bio-choice span,
.bio-choice small {
display: block;
}
.bio-choice small {
color: #6c757d;
margin-top: 3px;
}
.bio-choice.active,
.bio-number.active {
border-color: #188ae2;
background: #eaf5ff;
box-shadow: inset 0 0 0 1px #188ae2;
}
.bio-delete-form {
position: absolute;
top: 8px;
right: 8px;
}
.bio-number-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
gap: 8px;
}
.bio-number {
border: 1px solid #d7e3ef;
border-radius: 6px;
background: #f8fbff;
color: #1b3a57;
padding: 9px 6px;
cursor: pointer;
text-align: center;
}
.rack-box {
border: 1px solid #d7e3ef;
border-radius: 10px;
@@ -58,6 +124,7 @@
cursor: pointer;
text-align: center;
width: 100%;
min-height: 34px;
}
.slot-btn.slot-filled {
background: #fce8e8;
@@ -65,6 +132,10 @@
color: #8d1f1f;
cursor: pointer;
}
.slot-btn span,
.slot-btn small {
display: block;
}
.oldest-highlight {
border: 1px solid #ffd58f;
background: #fff8ea;
@@ -163,94 +234,10 @@
<div class="alert alert-warning m-b-20">Belum ada data spesimen.</div>
@endif
<h4 class="m-b-20">Visualisasi Lemari, Rack, Shelf, Box, dan Tube</h4>
<p class="text-muted">Klik nomor tube berwarna biru untuk mengisi spesimen. Tube merah artinya sudah terisi.</p>
<h4 class="m-b-20">Visualisasi Lemari, Rack, Slot, Shelf, Box, dan Tube</h4>
<p class="text-muted">Pilih posisi dari lemari sampai box. Tube biru bisa diisi, tube merah sudah terisi.</p>
@forelse($cabinets as $cabinet)
<div class="cabinet-visual">
<div class="d-flex justify-content-between align-items-start m-b-15">
<h5 class="m-b-0">{{ $cabinet->code }} - {{ $cabinet->name }} <small class="text-muted">({{ $cabinet->location ?? 'Lokasi belum diisi' }})</small></h5>
<form method="POST" action="{{ route('biorepository.deleteCabinet', $cabinet->id) }}" class="js-delete-cabinet">
@csrf
<button type="submit" class="btn btn-sm btn-danger">Delete Lemari</button>
</form>
</div>
<div class="rack-grid">
@forelse($cabinet->racks as $rack)
@php
$tubeMap = [];
foreach ($rack->specimens as $item) {
if ($item->shelf_number && $item->tube_number) {
$tubeMap[$item->shelf_number.'-'.$item->tube_number] = $item;
}
}
$shelfCount = (int) $rack->level;
$tubeCapacity = (int) $rack->capacity;
@endphp
<div class="rack-box">
<div class="d-flex justify-content-between align-items-start">
<div class="rack-header">{{ $rack->code }} - {{ $rack->name }}</div>
<form method="POST" action="{{ route('biorepository.deleteRack', $rack->id) }}" class="js-delete-rack">
@csrf
<button type="submit" class="btn btn-xs btn-danger">Delete Rack</button>
</form>
</div>
<div style="font-size:12px;">Rack No {{ $rack->rack_number ?? $rack->id }} | Total Shelf {{ $shelfCount }} | 1 Box per Shelf | Tube per Box {{ $tubeCapacity }}</div>
<div style="font-size:12px;">Terisi: {{ $rack->specimens->count() }}</div>
@if($shelfCount > 0 && $tubeCapacity > 0)
@for($shelf = 1; $shelf <= $shelfCount; $shelf++)
<div class="shelf-section">
<div style="font-size:12px;"><b>Shelf {{ $shelf }}</b> - Box 1 (Tube 1-{{ $tubeCapacity }})</div>
<div class="tube-grid">
@for($tube = 1; $tube <= $tubeCapacity; $tube++)
@php $tubeKey = $shelf.'-'.$tube; @endphp
@if(isset($tubeMap[$tubeKey]))
<button type="button"
class="slot-btn slot-filled js-slot-filled"
style="font-size:7px; line-height:1.1; padding:4px 2px;"
data-specimen-id="{{ $tubeMap[$tubeKey]->id }}"
data-rack-id="{{ $rack->id }}"
data-shelf="{{ $shelf }}"
data-rackno="{{ $rack->rack_number ?? $rack->id }}"
data-slot="{{ $shelf }}"
data-box="1"
data-tube="{{ $tube }}"
data-bakteri="{{ $tubeMap[$tubeKey]->bacteria_name ?? $tubeMap[$tubeKey]->specimen_name }}"
data-strain="{{ $tubeMap[$tubeKey]->strain }}"
data-volume="{{ $tubeMap[$tubeKey]->volume ?? 0 }}"
title="{{ $tubeMap[$tubeKey]->specimen_code }} - {{ $tubeMap[$tubeKey]->bacteria_name ?? $tubeMap[$tubeKey]->specimen_name }} / {{ $tubeMap[$tubeKey]->strain }}">
{{ ($tubeMap[$tubeKey]->bacteria_name ?? $tubeMap[$tubeKey]->specimen_name) }} / {{ $tubeMap[$tubeKey]->strain ?? '-' }}<br />
{{ $tubeMap[$tubeKey]->volume ?? 0 }} ml
</button>
@else
<button type="button"
class="slot-btn js-slot"
data-rack-id="{{ $rack->id }}"
data-shelf="{{ $shelf }}"
data-rackno="{{ $rack->rack_number ?? $rack->id }}"
data-slot="{{ $shelf }}"
data-box="1"
data-tube="{{ $tube }}">
{{ $tube }}
</button>
@endif
@endfor
</div>
</div>
@endfor
@else
<div class="alert alert-light m-b-0 m-t-10">Konfigurasi rack belum lengkap.</div>
@endif
</div>
@empty
<div class="alert alert-light m-b-0">Belum ada rack pada lemari ini.</div>
@endforelse
</div>
</div>
@empty
<div class="alert alert-warning">Belum ada data lemari biorepository.</div>
@endforelse
@livewire('biorepository-visual')
</div>
<div class="tab-pane" id="tabList">
@@ -405,15 +392,25 @@
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label>Jumlah Shelf per Rack (4-6)</label>
<label>Jumlah Slot per Rack</label>
<input type="number" name="slot_count" class="form-control" min="1" max="20" value="1" required>
</div>
<div class="form-group col-md-6">
<label>Jumlah Shelf per Slot (4-6)</label>
<input type="number" name="level" class="form-control" min="4" max="6" value="4" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label>Jumlah Box per Shelf</label>
<input type="number" name="box_number" class="form-control" min="1" max="20" value="1" required>
</div>
<div class="form-group col-md-6">
<label>Tube per Box (16-100)</label>
<input type="number" name="capacity" class="form-control" min="16" max="100" value="16" required>
</div>
</div>
<small class="text-muted">Rack number dibuat otomatis oleh sistem. Struktur: 1 rack memiliki 4-6 shelf, setiap shelf memiliki 1 box, setiap box 16-100 tube.</small>
<small class="text-muted">Rack number dibuat otomatis oleh sistem. Struktur: lemari -> rack -> slot -> shelf -> box -> tube.</small>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
@@ -459,11 +456,11 @@
<input type="number" class="form-control" id="raknomor" name="raknomor" readonly>
</div>
<div class="col-lg-4">
<label>Slot Number (Sama dengan Shelf)</label>
<label>Slot Number</label>
<input type="number" class="form-control" id="slotnomor" name="slotnomor" readonly>
</div>
<div class="col-lg-4">
<label>Box Number (1 per Shelf)</label>
<label>Box Number</label>
<input type="number" class="form-control" id="boxnomor" name="boxnomor" readonly>
</div>
<div class="col-lg-4">
@@ -28,6 +28,9 @@
<li class="nav-item">
<a href="#tab_hilang" data-toggle="tab" aria-expanded="false" class="nav-link">Stok Opname - Barang Hilang</a>
</li>
<li class="nav-item">
<a href="#tab_racikan" data-toggle="tab" aria-expanded="false" class="nav-link">Racikan</a>
</li>
<li class="nav-item">
<a href="#tab_statistik" data-toggle="tab" aria-expanded="false" class="nav-link">Statistik</a>
</li>
@@ -246,6 +249,190 @@
</div>
</div>
<div class="tab-pane" id="tab_racikan">
<div class="row">
<div class="col-lg-4">
<div class="card-box">
<h4 class="m-t-0">Proses Racikan</h4>
<div class="form-group">
<label>Tanggal Racik</label>
<input type="text" id="racik_tanggal" class="form-control" value="{{$tanggal}}">
</div>
<div class="form-group">
<label>Nama / Keterangan Racikan</label>
<input type="text" id="racik_nama" class="form-control" placeholder="contoh: Media LJ batch 01">
</div>
<div class="form-group">
<label>Bahan</label>
<select id="racik_bahan_pos" class="form-control">
<option value="" selected disabled>-- Pilih Bahan --</option>
@foreach($jjenis as $rjenis)
@php($kode = ($rjenis['kodejenis'] ?? '') ?: preg_replace('/\s+/', '', (string) ($rjenis['jenis'] ?? '')))
<option value="{{ $rjenis['jenis'] }}"
data-kodejenis="{{ $kode }}"
data-jenisid="{{ $rjenis['id'] ?? '' }}"
data-satuan="{{ $rjenis['satuan'] }}"
data-satuan-kecil="{{ $rjenis['satuan_kecil'] ?? '' }}"
data-konversi="{{ $rjenis['konversi_kecil'] ?? 1 }}">{{ $kode }} - {{ $rjenis['jenis'] }} ( {{ $rjenis['satuan'] }} )</option>
@endforeach
</select>
</div>
<div class="form-group">
<label>Satuan Bahan</label>
<select id="racik_bahan_satuan" class="form-control">
<option value="besar">Satuan Besar</option>
<option value="kecil">Satuan Kecil (Pecah Satuan)</option>
</select>
</div>
<div class="form-group">
<label>Jumlah Bahan Diambil</label>
<input type="text" id="racik_bahan_qty" class="form-control">
</div>
<div class="form-group">
<label>Batch Expired yang Dibuka (opsional)</label>
<select id="racik_bahan_batch" class="form-control">
<option value="">-- Pilih batch jika segel dibuka --</option>
</select>
</div>
<div class="form-group">
<label>Expired Baru Setelah Dibuka</label>
<input type="text" id="racik_bahan_expired_baru" class="form-control" placeholder="dd-mm-yyyy">
</div>
<div class="form-group">
<button type="button" class="btn btn-primary btn-block" id="btn_racik_add_bahan">Tambah Bahan</button>
</div>
</div>
<div class="card-box">
<h4 class="m-t-0">Update Expired Barang Terbuka</h4>
<div class="form-group">
<label>Barang</label>
<select id="expired_update_pos" class="form-control">
<option value="" selected disabled>-- Pilih Barang --</option>
@foreach($jjenis as $rjenis)
@php($kode = ($rjenis['kodejenis'] ?? '') ?: preg_replace('/\s+/', '', (string) ($rjenis['jenis'] ?? '')))
<option value="{{ $rjenis['jenis'] }}">{{ $kode }} - {{ $rjenis['jenis'] }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label>Batch Barang Masuk</label>
<select id="expired_update_batch" class="form-control">
<option value="">-- Pilih batch --</option>
</select>
</div>
<div class="form-group">
<label>Expired Baru</label>
<input type="text" id="expired_update_date" class="form-control" placeholder="dd-mm-yyyy">
</div>
<div class="form-group">
<label>Keterangan</label>
<input type="text" id="expired_update_note" class="form-control" placeholder="contoh: segel dibuka untuk racikan">
</div>
<button type="button" class="btn btn-warning btn-block" id="btn_expired_update">Update Expired Batch</button>
</div>
</div>
<div class="col-lg-8">
<div class="card-box">
<h4 class="m-t-0">Daftar Bahan Racikan</h4>
<div class="table-responsive">
<table class="table table-bordered table-striped m-b-0">
<thead>
<tr>
<th style="width: 110px">Kode</th>
<th>Bahan</th>
<th style="width: 120px">Qty</th>
<th style="width: 120px">Satuan</th>
<th>Expired Dibuka</th>
<th style="width: 90px">Aksi</th>
</tr>
</thead>
<tbody id="racik_lines">
<tr><td colspan="6" class="text-center text-muted">Belum ada bahan.</td></tr>
</tbody>
</table>
</div>
</div>
<div class="card-box">
<h4 class="m-t-0">Hasil Racikan</h4>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Status Racikan</label>
<select id="racik_status" class="form-control">
<option value="berhasil">Berhasil - masuk stok</option>
<option value="rusak">Rusak - hanya kurangi bahan</option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Jenis Hasil</label>
<select id="racik_hasil_mode" class="form-control">
<option value="existing">Barang sudah ada</option>
<option value="new">Barang baru</option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Masa Expired Hasil</label>
<input type="text" id="racik_hasil_expired" class="form-control" placeholder="dd-mm-yyyy">
</div>
</div>
</div>
<div id="racik_hasil_existing_fields">
<div class="form-group">
<label>Barang Hasil</label>
<select id="racik_hasil_pos" class="form-control">
<option value="" selected disabled>-- Pilih Barang Hasil --</option>
@foreach($jjenis as $rjenis)
@php($kode = ($rjenis['kodejenis'] ?? '') ?: preg_replace('/\s+/', '', (string) ($rjenis['jenis'] ?? '')))
<option value="{{ $rjenis['jenis'] }}"
data-kodejenis="{{ $kode }}"
data-jenisid="{{ $rjenis['id'] ?? '' }}"
data-satuan="{{ $rjenis['satuan'] }}"
data-satuan-kecil="{{ $rjenis['satuan_kecil'] ?? '' }}"
data-konversi="{{ $rjenis['konversi_kecil'] ?? 1 }}">{{ $kode }} - {{ $rjenis['jenis'] }} ( {{ $rjenis['satuan'] }} )</option>
@endforeach
</select>
</div>
</div>
<div id="racik_hasil_new_fields" style="display:none;">
<div class="row">
<div class="col-md-6"><div class="form-group"><label>Kode Barang Hasil</label><input type="text" id="racik_hasil_new_kode" class="form-control" maxlength="40"></div></div>
<div class="col-md-6"><div class="form-group"><label>Nama Barang Hasil</label><input type="text" id="racik_hasil_new_jenis" class="form-control"></div></div>
<div class="col-md-6"><div class="form-group"><label>Satuan Besar</label><input type="text" id="racik_hasil_new_satuan" class="form-control"></div></div>
<div class="col-md-6"><div class="form-group"><label>Satuan Kecil</label><input type="text" id="racik_hasil_new_satuan_kecil" class="form-control"></div></div>
<div class="col-md-6"><div class="form-group"><label>Konversi</label><input type="number" id="racik_hasil_new_konversi" class="form-control" value="1" min="1"></div></div>
<div class="col-md-6"><div class="form-group"><label>Stok Minimum</label><input type="number" id="racik_hasil_new_stok_minimum" class="form-control" value="0" min="0"></div></div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Jumlah Hasil</label>
<input type="text" id="racik_hasil_qty" class="form-control">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Satuan Hasil</label>
<select id="racik_hasil_satuan" class="form-control">
<option value="besar">Satuan Besar</option>
<option value="kecil">Satuan Kecil (Pecah Satuan)</option>
</select>
</div>
</div>
</div>
<div class="text-right">
<button type="button" class="btn btn-custom" id="btn_racik_commit">Simpan Proses Racikan</button>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="tab_report">
<div class="row">
<div class="col-md-12">
@@ -759,8 +946,12 @@
$("#edit_tanggal").datepicker({format: 'yyyy-mm-dd'});
$("#op_in_tanggal").datepicker({format: 'yyyy-mm-dd'});
$("#op_adj_tanggal").datepicker({format: 'yyyy-mm-dd'});
$("#racik_tanggal").datepicker({format: 'yyyy-mm-dd'});
$("#in_masa_expired").datepicker({format: 'dd-mm-yyyy'});
$("#op_in_masa_expired").datepicker({format: 'dd-mm-yyyy'});
$("#racik_bahan_expired_baru").datepicker({format: 'dd-mm-yyyy'});
$("#racik_hasil_expired").datepicker({format: 'dd-mm-yyyy'});
$("#expired_update_date").datepicker({format: 'dd-mm-yyyy'});
$("#edit_masa_expired").datepicker({format: 'dd-mm-yyyy'});
$("#mulai").datepicker({format: 'yyyy-mm-dd'});
$("#akhir").datepicker({format: 'yyyy-mm-dd'});
@@ -884,6 +1075,7 @@
var jenisRows = @json($jenisRows ?? []);
var opInLines = [];
var opAdjLines = [];
var racikLines = [];
var currentDetail = null;
function escapeHtml(value){
@@ -1128,6 +1320,32 @@
}
syncOpAdjSelected();
var currentRacikBahan = $('#racik_bahan_pos').val();
var htmlRacikBahan = '<option value=\"\" selected disabled>-- Pilih Bahan --</option>';
items.forEach(function(r){ htmlRacikBahan += makeOption(r); });
$('#racik_bahan_pos').html(htmlRacikBahan);
if(currentRacikBahan){
$('#racik_bahan_pos').val(currentRacikBahan);
}
syncRacikBahanSelected();
var currentRacikHasil = $('#racik_hasil_pos').val();
var htmlRacikHasil = '<option value=\"\" selected disabled>-- Pilih Barang Hasil --</option>';
items.forEach(function(r){ htmlRacikHasil += makeOption(r); });
$('#racik_hasil_pos').html(htmlRacikHasil);
if(currentRacikHasil){
$('#racik_hasil_pos').val(currentRacikHasil);
}
syncRacikHasilSelected();
var currentExpiredUpdate = $('#expired_update_pos').val();
var htmlExpiredUpdate = '<option value=\"\" selected disabled>-- Pilih Barang --</option>';
items.forEach(function(r){ htmlExpiredUpdate += makeOption(r); });
$('#expired_update_pos').html(htmlExpiredUpdate);
if(currentExpiredUpdate){
$('#expired_update_pos').val(currentExpiredUpdate);
}
// Legacy selects (modal lama / editor)
var legacyIds = ['#in_pos', '#out_pos'];
legacyIds.forEach(function(sel){
@@ -1323,10 +1541,14 @@
$('#out_pos').on('change', function(){ syncSatuanMode('#out_pos', '#out_satuan_transaksi'); });
$('#op_in_pos').on('change', function(){ syncSatuanMode('#op_in_pos', '#op_in_satuan_transaksi'); });
$('#op_adj_pos').on('change', function(){ syncSatuanMode('#op_adj_pos', '#op_adj_satuan_transaksi'); });
$('#racik_bahan_pos').on('change', function(){ syncRacikBahanSelected(); });
$('#racik_hasil_pos').on('change', function(){ syncRacikHasilSelected(); });
syncSatuanMode('#in_pos', '#in_satuan_transaksi');
syncSatuanMode('#out_pos', '#out_satuan_transaksi');
syncSatuanMode('#op_in_pos', '#op_in_satuan_transaksi');
syncSatuanMode('#op_adj_pos', '#op_adj_satuan_transaksi');
syncSatuanMode('#racik_bahan_pos', '#racik_bahan_satuan');
syncSatuanMode('#racik_hasil_pos', '#racik_hasil_satuan');
function syncOpInSelected(){
var val = $('#op_in_pos').val();
@@ -1371,6 +1593,56 @@
});
syncOpAdjSelected();
function loadExpiredBatches(jenis, targetSelect, selectedId){
var $target = $(targetSelect);
$target.html('<option value="">Memuat batch...</option>');
if(!jenis){
$target.html('<option value="">-- Pilih batch --</option>');
return;
}
$.ajax({
url: "{{ route('simbhp.expiredBatches') }}",
method: 'GET',
data: { jenis: jenis }
}).done(function(resp){
var rows = (resp && resp.data) ? resp.data : [];
var html = '<option value="">-- Pilih batch --</option>';
rows.forEach(function(row){
var label = (row.tanggal_masuk || '-') + ' | exp: ' + (row.masa_expired || '-') + ' | ' + (row.qty || 0) + ' ' + (row.satuan || '') + ' | ' + (row.deskripsi || '-');
html += '<option value="' + escapeHtml(row.id || '') + '" data-expired="' + escapeHtml(row.masa_expired || '') + '">' + escapeHtml(label) + '</option>';
});
$target.html(html);
if(selectedId){
$target.val(selectedId);
}
}).fail(function(){
$target.html('<option value="">Gagal memuat batch</option>');
});
}
function syncRacikBahanSelected(){
syncSatuanMode('#racik_bahan_pos', '#racik_bahan_satuan');
loadExpiredBatches($('#racik_bahan_pos').val(), '#racik_bahan_batch');
}
function syncRacikHasilSelected(){
syncSatuanMode('#racik_hasil_pos', '#racik_hasil_satuan');
}
syncRacikBahanSelected();
syncRacikHasilSelected();
$('#expired_update_pos').on('change', function(){
loadExpiredBatches($(this).val(), '#expired_update_batch');
});
$('#expired_update_batch').on('change', function(){
var oldExpired = $(this).find('option:selected').data('expired') || '';
if(oldExpired && !$('#expired_update_date').val()){
$('#expired_update_date').val(oldExpired);
}
});
$('#op_in_kode_barang').on('input', function(){
if($('#op_in_pos').val() !== '__NEW__'){ return; }
$(this).val(normalizeKode($(this).val()));
@@ -1390,6 +1662,38 @@
$('#op_in_new_satuan_kecil').on('input', syncOpInNewSatuanAvailability);
$('#op_in_new_konversi').on('input', syncOpInNewSatuanAvailability);
function syncRacikHasilMode(){
var isNew = $('#racik_hasil_mode').val() === 'new';
$('#racik_hasil_existing_fields').toggle(!isNew);
$('#racik_hasil_new_fields').toggle(isNew);
if(isNew){
var satuanKecil = ($('#racik_hasil_new_satuan_kecil').val() || '').toString().trim();
var konv = parseInt($('#racik_hasil_new_konversi').val() || 1, 10);
var hasBreakdown = satuanKecil !== '' && konv > 1;
$('#racik_hasil_satuan option[value="kecil"]').prop('disabled', !hasBreakdown);
if(!hasBreakdown && $('#racik_hasil_satuan').val() === 'kecil'){
$('#racik_hasil_satuan').val('besar');
}
}else{
syncRacikHasilSelected();
}
}
$('#racik_hasil_mode').on('change', syncRacikHasilMode);
$('#racik_status').on('change', function(){
var isRusak = $(this).val() === 'rusak';
$('#racik_hasil_mode, #racik_hasil_pos, #racik_hasil_expired, #racik_hasil_qty, #racik_hasil_satuan').prop('disabled', isRusak);
$('#racik_hasil_new_fields input').prop('disabled', isRusak);
});
$('#racik_hasil_new_satuan_kecil, #racik_hasil_new_konversi').on('input', syncRacikHasilMode);
$('#racik_hasil_new_kode').on('input', function(){ $(this).val(normalizeKode($(this).val())); });
$('#racik_hasil_new_jenis').on('input', function(){
if(($('#racik_hasil_new_kode').val() || '') === ''){
$('#racik_hasil_new_kode').val(normalizeKode($(this).val()));
}
});
syncRacikHasilMode();
$('#op_in_satuan_transaksi').on('change', function(){
if($('#op_in_pos').val() === '__NEW__'){ return; }
var selected = $('#op_in_pos option:selected');
@@ -1713,6 +2017,8 @@
$("#out_total").autoNumeric( 'init', {aSep: ',', mDec: '0', vMax: '99999999999999999999999999'} );
$("#op_in_total").autoNumeric( 'init', {aSep: ',', mDec: '0', vMax: '99999999999999999999999999'} );
$("#op_adj_total").autoNumeric( 'init', {aSep: ',', mDec: '0', vMax: '99999999999999999999999999'} );
$("#racik_bahan_qty").autoNumeric( 'init', {aSep: ',', mDec: '0', vMax: '99999999999999999999999999'} );
$("#racik_hasil_qty").autoNumeric( 'init', {aSep: ',', mDec: '0', vMax: '99999999999999999999999999'} );
function renderOpInLines(){
if(!opInLines.length){
@@ -1752,6 +2058,29 @@
$('#op_adj_lines').html(html);
}
function renderRacikLines(){
if(!racikLines.length){
$('#racik_lines').html('<tr><td colspan="6" class="text-center text-muted">Belum ada bahan.</td></tr>');
return;
}
var html = '';
racikLines.forEach(function(line, idx){
var expiredInfo = '-';
if(line.batch_label || line.expired_baru){
expiredInfo = (line.batch_label || '-') + (line.expired_baru ? '<br><small>baru: ' + escapeHtml(line.expired_baru) + '</small>' : '');
}
html += '<tr>' +
'<td class="text-center"><code>' + escapeHtml(line.kode || '') + '</code></td>' +
'<td>' + escapeHtml(line.jenis || '') + '</td>' +
'<td class="text-right">' + escapeHtml(line.qty || '') + '</td>' +
'<td class="text-center">' + escapeHtml(line.satuan_transaksi || '') + '</td>' +
'<td>' + expiredInfo + '</td>' +
'<td class="text-center"><button type="button" class="btn btn-sm btn-danger btn_racik_remove" data-index="' + idx + '">Hapus</button></td>' +
'</tr>';
});
$('#racik_lines').html(html);
}
function getSelectedKode(selectId){
var selected = $(selectId).find('option:selected');
return normalizeKode(selected.data('kodejenis') || '');
@@ -1904,6 +2233,43 @@
if(!isNaN(idx)){ opAdjLines.splice(idx, 1); renderOpAdjLines(); }
});
$('#btn_racik_add_bahan').on('click', function(){
var jenis = $('#racik_bahan_pos').val();
var qty = $('#racik_bahan_qty').val();
var satuanTransaksi = $('#racik_bahan_satuan').val() || 'besar';
var kode = getSelectedKode('#racik_bahan_pos');
var batchId = $('#racik_bahan_batch').val();
var batchLabel = batchId ? $('#racik_bahan_batch option:selected').text() : '';
var expiredBaru = $('#racik_bahan_expired_baru').val();
if(!jenis || !qty){
$.toast({ heading: 'Gagal', text: 'Bahan dan jumlah wajib diisi', position: 'top-right', loaderBg: '#bf441d', icon: 'error', hideAfter: 3000, stack: 1 });
return;
}
if(expiredBaru && !batchId){
$.toast({ heading: 'Gagal', text: 'Pilih batch yang dibuka jika mengisi expired baru', position: 'top-right', loaderBg: '#bf441d', icon: 'error', hideAfter: 3500, stack: 1 });
return;
}
racikLines.push({
jenis: jenis,
kode: kode,
qty: qty,
satuan_transaksi: satuanTransaksi,
batch_id: batchId,
batch_label: batchLabel,
expired_baru: expiredBaru
});
$('#racik_bahan_qty').val('');
$('#racik_bahan_expired_baru').val('');
renderRacikLines();
});
$(document).on('click', '.btn_racik_remove', function(){
var idx = parseInt($(this).data('index'), 10);
if(!isNaN(idx)){ racikLines.splice(idx, 1); renderRacikLines(); }
});
function runBatchPost(lines, buildPayload, onDone){
var i = 0;
function next(){
@@ -2083,8 +2449,120 @@
nextAdj();
});
$('#btn_racik_commit').on('click', function(){
if(!racikLines.length){
$.toast({ heading: 'Info', text: 'Daftar bahan racikan masih kosong', position: 'top-right', loaderBg: '#3b98b5', icon: 'info', hideAfter: 2500, stack: 1 });
return;
}
var statusRacikan = $('#racik_status').val();
var payload = {
_token: token,
tanggal: $('#racik_tanggal').val(),
nama_racikan: $('#racik_nama').val(),
status_racikan: statusRacikan,
bahan: JSON.stringify(racikLines),
hasil_mode: $('#racik_hasil_mode').val(),
hasil_jenis: $('#racik_hasil_pos').val(),
hasil_qty: $('#racik_hasil_qty').val(),
hasil_satuan_transaksi: $('#racik_hasil_satuan').val(),
hasil_masa_expired: $('#racik_hasil_expired').val(),
hasil_new_kode: $('#racik_hasil_new_kode').val(),
hasil_new_jenis: $('#racik_hasil_new_jenis').val(),
hasil_new_satuan: $('#racik_hasil_new_satuan').val(),
hasil_new_satuan_kecil: $('#racik_hasil_new_satuan_kecil').val(),
hasil_new_konversi: $('#racik_hasil_new_konversi').val(),
hasil_new_stok_minimum: $('#racik_hasil_new_stok_minimum').val()
};
if(statusRacikan === 'berhasil'){
if(payload.hasil_mode === 'existing' && !payload.hasil_jenis){
$.toast({ heading: 'Gagal', text: 'Barang hasil racikan wajib dipilih', position: 'top-right', loaderBg: '#bf441d', icon: 'error', hideAfter: 3000, stack: 1 });
return;
}
if(!payload.hasil_qty){
$.toast({ heading: 'Gagal', text: 'Jumlah hasil racikan wajib diisi', position: 'top-right', loaderBg: '#bf441d', icon: 'error', hideAfter: 3000, stack: 1 });
return;
}
}
var $btn = $(this);
$btn.prop('disabled', true).text('Menyimpan...');
$.ajax({ url: "{{ route('simbhp.racikan') }}", method: 'POST', data: payload })
.done(function(resp){
var ok = resp && resp.status && resp.status.toString().toLowerCase() === 'success';
$.toast({
heading: ok ? 'Success' : 'Gagal',
text: (resp && resp.message) ? resp.message : (ok ? 'Racikan tersimpan' : 'Gagal menyimpan racikan'),
position: 'top-right',
loaderBg: ok ? '#5ba035' : '#bf441d',
icon: ok ? 'success' : 'error',
hideAfter: 4500,
stack: 1
});
if(ok){
racikLines = [];
renderRacikLines();
$('#racik_hasil_qty').val('');
$('#racik_hasil_expired').val('');
if(typeof window.gudangRefresh === 'function'){
window.gudangRefresh();
}
}
})
.fail(function(){
$.toast({ heading: 'Gagal', text: 'Request gagal', position: 'top-right', loaderBg: '#bf441d', icon: 'error', hideAfter: 4500, stack: 1 });
})
.always(function(){
$btn.prop('disabled', false).text('Simpan Proses Racikan');
});
});
$('#btn_expired_update').on('click', function(){
var batchId = $('#expired_update_batch').val();
var newDate = $('#expired_update_date').val();
if(!batchId || !newDate){
$.toast({ heading: 'Gagal', text: 'Batch dan expired baru wajib diisi', position: 'top-right', loaderBg: '#bf441d', icon: 'error', hideAfter: 3000, stack: 1 });
return;
}
var $btn = $(this);
$btn.prop('disabled', true).text('Menyimpan...');
$.ajax({
url: "{{ route('simbhp.updateExpired') }}",
method: 'POST',
data: {
_token: token,
batch_id: batchId,
masa_expired: newDate,
alasan: $('#expired_update_note').val()
}
}).done(function(resp){
var ok = resp && resp.status && resp.status.toString().toLowerCase() === 'success';
$.toast({
heading: ok ? 'Success' : 'Gagal',
text: (resp && resp.message) ? resp.message : 'Gagal update expired',
position: 'top-right',
loaderBg: ok ? '#5ba035' : '#bf441d',
icon: ok ? 'success' : 'error',
hideAfter: 3500,
stack: 1
});
if(ok){
loadExpiredBatches($('#expired_update_pos').val(), '#expired_update_batch');
if(typeof window.gudangRefresh === 'function'){
window.gudangRefresh();
}
}
}).fail(function(){
$.toast({ heading: 'Gagal', text: 'Request gagal', position: 'top-right', loaderBg: '#bf441d', icon: 'error', hideAfter: 4500, stack: 1 });
}).always(function(){
$btn.prop('disabled', false).text('Update Expired Batch');
});
});
renderOpInLines();
renderOpAdjLines();
renderRacikLines();
$('#btnexportreport').click(function(){
var gridContent = $("#gridreportblnini").jqxGrid('exportdata', 'json');
data = $.parseJSON(gridContent);
+41 -8
View File
@@ -75,6 +75,20 @@
<input class="form-control" type="number" id="add_modaliti2" name="add_modaliti2">
</div>
</div>
<div class="form-group m-b-25">
<div class="col-12">
<label>Themplate</label>
<select id="add_kelompok" class="form-control">
<option value="CCI">PCCI</option>
<option value="Kultur">Kultur</option>
<option value="Pewarna Langsung">Pewarna Langsung</option>
<option value="TBC">TBC</option>
<option value="Viral Load">Viral Load</option>
<option value="IgM IgG Leptospira">IgM IgG Leptospira</option>
<option value="PCR COVID">PCR COVID</option>
</select>
</div>
</div>
<div class="form-group account-btn text-center m-t-10">
<div class="col-12">
<button class="btn w-lg btn-custom waves-effect waves-light" id="btnAddPoli" type="button">Save</button>
@@ -125,6 +139,20 @@
<input class="form-control" type="number" id="modaliti2" name="modaliti2">
</div>
</div>
<div class="form-group m-b-25">
<div class="col-12">
<label>Themplate</label>
<select id="kelompok" class="form-control">
<option value="CCI">PCCI</option>
<option value="Kultur">Kultur</option>
<option value="Pewarna Langsung">Pewarna Langsung</option>
<option value="TBC">TBC</option>
<option value="Viral Load">Viral Load</option>
<option value="IgM IgG Leptospira">IgM IgG Leptospira</option>
<option value="PCR COVID">PCR COVID</option>
</select>
</div>
</div>
<input type="hidden" id="id_poli" name="id_poli">
<div class="form-group account-btn text-center m-t-10">
<div class="col-12">
@@ -390,7 +418,8 @@
{ name: 'subpoli', type: 'text'},
{ name: 'subsubpoli', type: 'text'},
{ name: 'modaliti', type: 'text'},
{ name: 'modaliti2', type: 'text'}
{ name: 'modaliti2', type: 'text'},
{ name: 'kelompok', type: 'text'},
],
updaterow: function (rowid, rowdata, commit) {
commit(true);
@@ -410,11 +439,12 @@
theme : "energyblue",
selectionmode : 'multiplecellsextended',
columns : [
{ text: 'Category', filtertype: 'checkedlist', datafield: 'poli', width: '15%', align: 'center', cellsalign: 'left'},
{ text: 'Request Name', datafield: 'subpoli', width: '35%', cellsalign: 'left', align: 'center' },
{ text: 'Request Name', datafield: 'subpoli', width: '30%', cellsalign: 'left', align: 'center' },
{ text: 'Header', datafield: 'modaliti', width: '15%', cellsalign: 'left', align: 'center' },
{ text: 'Footer', datafield: 'subsubpoli', width: '15%', cellsalign: 'left', align: 'center' },
{ text: 'Update', sortable: false, filterable: false, columntype: 'button', align: 'center', width: '10%', cellsrenderer: function () {
{ text: 'Group', datafield: 'kelompok', width: '15%', cellsalign: 'left', align: 'center' },
{ text: 'Target Selesai', datafield: 'modaliti2', width: '7%', cellsalign: 'center', align: 'center'},
{ text: 'Update', sortable: false, filterable: false, columntype: 'button', align: 'center', width: '8%', cellsrenderer: function () {
return "Update";
}, buttonclick: function (row) {
editrowpoli = row;
@@ -424,6 +454,7 @@
$("#poli").val(dataRecord.poli);
$("#subpoli").val(dataRecord.subpoli);
$("#subsubpoli").val(dataRecord.subsubpoli);
$("#kelompok").val(dataRecord.kelompok);
$("#modaliti").val(dataRecord.modaliti);
$("#modaliti2").val(dataRecord.modaliti2);
$('#updateModalPoli').modal('show');
@@ -664,15 +695,16 @@
var subsubpoli = document.getElementById('add_subsubpoli').value;
var modaliti = document.getElementById('add_modaliti').value;
var modaliti2 = document.getElementById('add_modaliti2').value;
var kelompok = document.getElementById('add_kelompok').value;
var token = document.getElementById('token').value;
if (poli == '' || subpoli == '' || subsubpoli == '' || modaliti == '' || modaliti2 == '' || modaliti2 == '0'){
if (poli == '' || subpoli == '' || subsubpoli == '' || modaliti == '' || modaliti2 == '' || modaliti2 == '0' || kelompok == ''){
swal({
title : 'Mohon diLengkapi',
text : 'Data Dasar Wajib di Isi, Target Hari tidak Boleh Kosong / 0',
type : 'warning',
});
} else {
$.post('{{ route("storePoli") }}', { poli: poli, subpoli: subpoli, subsubpoli: subsubpoli, modaliti: modaliti, modaliti2: modaliti2, _token: token },
$.post('{{ route("storePoli") }}', { poli: poli, subpoli: subpoli, subsubpoli: subsubpoli, modaliti: modaliti, modaliti2: modaliti2, kelompok: kelompok, _token: token },
function(data){
var message = data.message;
var status = data.status;
@@ -719,15 +751,16 @@
var poli_id = document.getElementById('id_poli').value;
var modaliti = document.getElementById('modaliti').value;
var modaliti2 = document.getElementById('modaliti2').value;
var kelompok = document.getElementById('kelompok').value;
var token = document.getElementById('token').value;
if (poli == '' || subpoli == '' || subsubpoli == '' || modaliti == '' || modaliti2 == '' || modaliti2 == '0'){
if (poli == '' || subpoli == '' || subsubpoli == '' || modaliti == '' || modaliti2 == '' || modaliti2 == '0' || kelompok == ''){
swal({
title : 'Mohon diLengkapi',
text : 'Data Dasar Wajib di Isi, Target Hari tidak Boleh Kosong / 0',
type : 'warning',
});
} else {
$.post('{{ route("updatePoli") }}', { id: poli_id, poli: poli, subpoli: subpoli, subsubpoli: subsubpoli, modaliti: modaliti, modaliti2: modaliti2, _token: token },
$.post('{{ route("updatePoli") }}', { id: poli_id, poli: poli, subpoli: subpoli, subsubpoli: subsubpoli, modaliti: modaliti, modaliti2: modaliti2, kelompok: kelompok, _token: token },
function(data){
var message = data.message;
var status = data.status;
+22 -1
View File
@@ -72,6 +72,9 @@
<div class="form-group col-md-3">
<button type="button" class="btn btn-primary btn-block" id="btnrekapzn">Rekap Ziehl Nielsen</button>
</div>
<div class="form-group col-md-3">
<button type="button" class="btn btn-secondary btn-block" id="btnmikroskopisbtasp">Mikroskopis BTA SP</button>
</div>
<div class="form-group col-md-3">
<a href="{{url('/biorepository')}}" class="btn btn-danger btn-block">Biorepository Bakteri</a>
</div>
@@ -225,6 +228,7 @@
<th>Hasil MDR</th>
<th>Sample Masuk</th>
<th>Verifikasi</th>
<th>Tgl. Preliminary</th>
<th>Target</th>
<th>Duration</th>
<th>Keterangan</th>
@@ -290,6 +294,7 @@ $(document).ready(function () {
{ name: 'kd_spesimen', type: 'text'},
{ name: 'nm_spesimen', type: 'text'},
{ name: 'verifikasi',type: 'text'},
{ name: 'tgl_preliminary',type: 'text'},
{ name: 'dlp',type: 'text'},
{ name: 'status',type: 'text'},
{ name: 'id_bakteri01',type: 'text'},
@@ -351,6 +356,7 @@ $(document).ready(function () {
{ text: 'Hasil MDR', datafield: 'hasil_mdr', width: 180, cellsalign: 'left', align: 'center', hidden: jenisReport !== 'list_hasil_mdr'},
{ text: 'Tgl. Sample', datafield: 'mulai', width: 160, cellsalign: 'left', align: 'center' },
{ text: 'Tgl. Hasil', datafield: 'verifikasi', width: 160, cellsalign: 'left', align: 'center' },
{ text: 'Tgl. Preliminary', datafield: 'tgl_preliminary', width: 160, cellsalign: 'left', align: 'center' },
{ text: 'Target', datafield: 'modaliti2', width: 100, cellsalign: 'left', align: 'center'},
{ text: 'Duration', datafield: 'selisih_hari', width: 100, cellsalign: 'left', align: 'center'},
{ text: 'Keterangan', datafield: 'status_target', width: 100, cellsalign: 'left', align: 'center'},
@@ -392,6 +398,7 @@ $(document).ready(function () {
<td>${row.hasil_mdr ?? ''}</td>
<td>${row.mulai ?? ''}</td>
<td>${row.verifikasi ?? ''}</td>
<td>${row.tgl_preliminary ?? ''}</td>
<td>${row.modaliti2 ?? ''}</td>
<td>${row.selisih_hari ?? ''}</td>
<td>${row.status_target ?? ''}</td>
@@ -654,6 +661,20 @@ $(document).ready(function () {
var url = '{{url('/')}}/znreport?bulan='+bulan+'?tahun='+tahun;
window.location.href = url;
}
});
$('#btnmikroskopisbtasp').click(function () {
var bulan = document.getElementById('bulan').value;
var tahun = document.getElementById('tahun').value;
if (bulan == '' || tahun == ''){
swal({
title : 'Mohon diLengkapi',
text : 'Data Dasar Bulan dan Tahun Wajib di Isi',
type : 'error',
});
} else {
var url = '{{url('/')}}/mikroskopisbtasp?bulan='+bulan+'&tahun='+tahun;
window.location.href = url;
}
});
$('#btnrekaptat').click(function () {
var bulan = document.getElementById('bulan').value;
@@ -666,7 +687,7 @@ $(document).ready(function () {
type : 'error',
});
} else {
var url = '{{url('/')}}/tatreport?bulan='+bulan+'?tahun='+tahun;
var url = '{{url('/')}}/tatreport?bulan='+bulan+'&tahun='+tahun;
window.location.href = url;
}
});
@@ -258,11 +258,16 @@
@if(Session::get('id') !== null)
@push('script')
<script type="text/javascript">
(function () {
var refreshUrl = "{{ route('criticalValueNotificationSummary') }}";
var refreshIntervalMs = 20000;
var inFlight = null;
<script type="text/javascript">
(function () {
if (window.criticalValueNotificationBellInitialized) {
return;
}
window.criticalValueNotificationBellInitialized = true;
var refreshUrl = "{{ route('criticalValueNotificationSummary') }}";
var refreshIntervalMs = 20000;
var inFlight = null;
function setBadge(count) {
var $badge = $('#notificationcount');
@@ -290,13 +295,13 @@
});
}
$(function () {
setBadge(parseInt($('#notificationcount').text(), 10) || 0);
refreshCriticalBell();
setInterval(refreshCriticalBell, refreshIntervalMs);
$('#notification-bell-icon').closest('.dropdown').on('shown.bs.dropdown', refreshCriticalBell);
});
})();
</script>
$(function () {
setBadge(parseInt($('#notificationcount').text(), 10) || 0);
refreshCriticalBell();
window.criticalValueNotificationBellTimer = setInterval(refreshCriticalBell, refreshIntervalMs);
$('#notification-bell-icon').closest('.dropdown').off('shown.bs.dropdown.critical-bell').on('shown.bs.dropdown.critical-bell', refreshCriticalBell);
});
})();
</script>
@endpush
@endif
@@ -50,9 +50,9 @@
<td valign="top">No.Rekam Medik</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->noregister }}</td>
<td valign="top">Diagnosa</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->klinis !!}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
</tr>
<tr>
@@ -104,7 +104,11 @@
if (trim($rows->komponen) == 'bakteri') {$bakteri = $rows->isidata; }
}
}
if ($periksa->kd_spesimen == 'KULTUR JMR' OR $periksa->kd_spesimen == 'JAMUR'){
$teksmikroorganisme = 'Jamur yang ditemukan : ';
} else {
$teksmikroorganisme = 'Bakteri yang ditemukan : ';
}
@endphp
<table style="width:100%">
<tr><td colspan="3">Pemeriksaan Candida Colonization Index :</td></tr>
@@ -141,7 +145,7 @@
@endif
@if (!isset($cekketerangan[1]) && isset($antibiotiks) && $antibiotiks->isNotEmpty())
@if ($bakteri != '')
<p>Mikroorganisme yang ditemukan : <strong><i>{{$bakteri}}</i></strong> {{$data['bakterisir'] ?? ''}}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{$bakteri}}</i></strong> {{$data['bakterisir'] ?? ''}}</p>
@endif
@php
// Filter antibiotik yang printrow == true
@@ -49,9 +49,9 @@
<td valign="top">Jenis Kelamin</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->jkpasien }}</td>
<td valign="top">Diagnosa</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->klinis !!}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
</tr>
<tr>
@@ -94,6 +94,11 @@
$namakuman = '';
$namakuman1 = '';
$namakuman2 = '';
if ($periksa->kd_spesimen == 'KULTUR JMR' OR $periksa->kd_spesimen == 'JAMUR'){
$teksmikroorganisme = 'Jamur yang ditemukan : ';
} else {
$teksmikroorganisme = 'Bakteri yang ditemukan : ';
}
@endphp
@if(isset($iscritical->id))
<p><strong><i>*NILAI KRITIS</i></strong></p>
@@ -107,16 +112,16 @@
if (isset($data['bakteri'])) {
if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK') {
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
$namakuman = $data['bakteri'];
}
}
if (isset($data['id_bakteri01']) AND $namakuman != $data['id_bakteri01']) {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
$namakuman1 = $data['id_bakteri01'];
}
if (isset($data['id_bakteri02']) AND $namakuman != $data['id_bakteri02']) {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
$namakuman2 = $data['id_bakteri02'];
}
}
@@ -129,7 +134,7 @@
@if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK')
@else
@if ($sudahcetak1 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['bakteri'] }}</i></strong> {{ $data['bakterisir'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['bakteri'] }}</i></strong> {{ $data['bakterisir'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol']))
@@ -147,7 +152,7 @@
@if (isset($data['id_bakteri01cetak']) AND $data['id_bakteri01cetak'] == 'TIDAK')
@else
@if ($sudahcetak2 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['id_bakteri01'] }}</i></strong> {{ $data['id_bakterisir01'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['id_bakteri01'] }}</i></strong> {{ $data['id_bakterisir01'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks01']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks01'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol01']))
@@ -165,7 +170,7 @@
@if (isset($data['id_bakteri02cetak']) AND $data['id_bakteri02cetak'] == 'TIDAK')
@else
@if ($sudahcetak3 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['id_bakteri02'] }}</i></strong> {{ $data['id_bakterisir02'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['id_bakteri02'] }}</i></strong> {{ $data['id_bakterisir02'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks02']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks02'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol02']))
@@ -235,7 +240,7 @@
if (isset($data['bakteri']) AND $namakuman == '') {
if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK') {
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol'])){
@@ -247,7 +252,7 @@
if (isset($data['id_bakteri01cetak']) AND $data['id_bakteri01cetak'] == 'TIDAK'){
} else {
echo "<p>Bakteri Yang ditemukan : <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks01'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks01']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol01'])){
@@ -259,7 +264,7 @@
if (isset($data['id_bakteri02cetak']) AND $data['id_bakteri02cetak'] == 'TIDAK'){
} else{
echo "<p>Bakteri Yang ditemukan : <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks02'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks02']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol02'])){
@@ -68,9 +68,9 @@
<td valign="top">No.Rekam Medik</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->noregister }}</td>
<td valign="top">Diagnosa</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->klinis !!}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
</tr>
<tr>
@@ -353,6 +353,11 @@
$namakuman = '';
$namakuman1 = '';
$namakuman2 = '';
if ($periksa->kd_spesimen == 'KULTUR JMR' OR $periksa->kd_spesimen == 'JAMUR'){
$teksmikroorganisme = 'Jamur yang ditemukan : ';
} else {
$teksmikroorganisme = 'Bakteri yang ditemukan : ';
}
@endphp
@if(isset($iscritical->id))
<p><strong><i>*NILAI KRITIS</i></strong></p>
@@ -366,16 +371,16 @@
if (isset($data['bakteri'])) {
if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK') {
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
$namakuman = $data['bakteri'];
}
}
if (isset($data['id_bakteri01']) AND $namakuman != $data['id_bakteri01']) {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
$namakuman1 = $data['id_bakteri01'];
}
if (isset($data['id_bakteri02']) AND $namakuman != $data['id_bakteri02']) {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
$namakuman2 = $data['id_bakteri02'];
}
}
@@ -388,7 +393,7 @@
@if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK')
@else
@if ($sudahcetak1 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['bakteri'] }}</i></strong> {{ $data['bakterisir'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['bakteri'] }}</i></strong> {{ $data['bakterisir'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol']))
@@ -406,7 +411,7 @@
@if (isset($data['id_bakteri01cetak']) AND $data['id_bakteri01cetak'] == 'TIDAK')
@else
@if ($sudahcetak2 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['id_bakteri01'] }}</i></strong> {{ $data['id_bakterisir01'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['id_bakteri01'] }}</i></strong> {{ $data['id_bakterisir01'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks01']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks01'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol01']))
@@ -424,7 +429,7 @@
@if (isset($data['id_bakteri02cetak']) AND $data['id_bakteri02cetak'] == 'TIDAK')
@else
@if ($sudahcetak3 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['id_bakteri02'] }}</i></strong> {{ $data['id_bakterisir02'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['id_bakteri02'] }}</i></strong> {{ $data['id_bakterisir02'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks02']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks02'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol02']))
@@ -444,7 +449,7 @@
$left = $items->slice(0, $half)->values();
$right = $items->slice($half)->values();
@endphp
@if ($statuspemeriksaan == 'Preliminary Results')
@if ($statuspemeriksaan == 'Preliminary Results' AND $tabel != 'GenerateHTMLFinal')
<p><em><strong>Uji Kepekaan antibiotik on going</strong></em></p>
@else
@if ($cetakjudul)
@@ -498,7 +503,7 @@
if (isset($data['bakteri']) AND $namakuman == '') {
if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK') {
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol'])){
@@ -510,7 +515,7 @@
if (isset($data['id_bakteri01cetak']) AND $data['id_bakteri01cetak'] == 'TIDAK'){
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks01'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks01']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol01'])){
@@ -522,7 +527,7 @@
if (isset($data['id_bakteri02cetak']) AND $data['id_bakteri02cetak'] == 'TIDAK'){
} else{
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks02'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks02']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol02'])){
@@ -556,6 +561,6 @@
<td valign="top" colspan="3" align="center">{{ $periksa->nmdokter }}</td>
</tr>
</table>
<font color="white">KULTUR</font>
<font color="white">KULTUR {{ $tabel }}</font>
</body>
</html>
@@ -50,9 +50,9 @@
<td valign="top">No.Rekam Medik</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->noregister }}</td>
<td valign="top">Diagnosa</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->klinis !!}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
</tr>
<tr>
@@ -196,13 +196,18 @@
</table>
@php
$cekketerangan = explode('/table', $keterangan);
if ($periksa->kd_spesimen == 'KULTUR JMR' OR $periksa->kd_spesimen == 'JAMUR'){
$teksmikroorganisme = 'Jamur yang ditemukan : ';
} else {
$teksmikroorganisme = 'Bakteri yang ditemukan : ';
}
@endphp
@if(isset($iscritical->id))
<p><strong><i>*NILAI KRITIS</i></strong></p>
@endif
@if (!isset($cekketerangan[1]) && isset($antibiotiks) && $antibiotiks->isNotEmpty())
@if ($bakteri != '')
<p>Mikroorganisme yang ditemukan : <strong><i>{{$bakteri}}</i></strong> {{$data['bakterisir'] ?? ''}}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{$bakteri}}</i></strong> {{$data['bakterisir'] ?? ''}}</p>
@endif
@php
// Filter antibiotik yang printrow == true
@@ -49,9 +49,9 @@
<td valign="top">No.Rekam Medik</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->noregister }}</td>
<td valign="top">Diagnosa</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->klinis !!}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
</tr>
<tr>
@@ -333,6 +333,11 @@
$namakuman = '';
$namakuman1 = '';
$namakuman2 = '';
if ($periksa->kd_spesimen == 'KULTUR JMR' OR $periksa->kd_spesimen == 'JAMUR'){
$teksmikroorganisme = 'Jamur yang ditemukan : ';
} else {
$teksmikroorganisme = 'Bakteri yang ditemukan : ';
}
@endphp
@if(isset($iscritical->id))
<p><strong><i>Critical Value</i></strong></p>
@@ -346,16 +351,16 @@
if (isset($data['bakteri'])) {
if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK') {
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
$namakuman = $data['bakteri'];
}
}
if (isset($data['id_bakteri01']) AND $namakuman != $data['id_bakteri01']) {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
$namakuman1 = $data['id_bakteri01'];
}
if (isset($data['id_bakteri02']) AND $namakuman != $data['id_bakteri02']) {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
$namakuman2 = $data['id_bakteri02'];
}
}
@@ -368,7 +373,7 @@
@if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK')
@else
@if ($sudahcetak1 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['bakteri'] }}</i></strong> {{ $data['bakterisir'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['bakteri'] }}</i></strong> {{ $data['bakterisir'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol']))
@@ -386,7 +391,7 @@
@if (isset($data['id_bakteri01cetak']) AND $data['id_bakteri01cetak'] == 'TIDAK')
@else
@if ($sudahcetak2 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['id_bakteri01'] }}</i></strong> {{ $data['id_bakterisir01'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['id_bakteri01'] }}</i></strong> {{ $data['id_bakterisir01'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks01']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks01'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol01']))
@@ -404,7 +409,7 @@
@if (isset($data['id_bakteri02cetak']) AND $data['id_bakteri02cetak'] == 'TIDAK')
@else
@if ($sudahcetak3 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['id_bakteri02'] }}</i></strong> {{ $data['id_bakterisir02'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['id_bakteri02'] }}</i></strong> {{ $data['id_bakterisir02'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks02']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks02'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol02']))
@@ -474,7 +479,7 @@
if (isset($data['bakteri']) AND $namakuman == '') {
if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK') {
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol'])){
@@ -486,7 +491,7 @@
if (isset($data['id_bakteri01cetak']) AND $data['id_bakteri01cetak'] == 'TIDAK'){
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks01'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks01']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol01'])){
@@ -498,7 +503,7 @@
if (isset($data['id_bakteri02cetak']) AND $data['id_bakteri02cetak'] == 'TIDAK'){
} else{
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks02'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks02']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol02'])){
@@ -65,9 +65,9 @@
<td valign="top">No.Rekam Medik</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->noregister }}</td>
<td valign="top">Diagnosa</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->klinis !!}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
</tr>
<tr>
@@ -100,12 +100,6 @@
<td valign="top" colspan="2">&nbsp;</td>
</tr>
<tr><td colspan="8">&nbsp;</td></tr>
<tr>
<td width="45">&nbsp;</td>
<td valign="top" colspan="7" width="755">
<h1>HASIL PEMERIKSAAN LABORATORIUM MIKROBIOLOGI KLINIK</h1>
</td>
</tr>
<tr>
<td width="45">&nbsp;</td>
<td valign="top" colspan="7" width="755">
@@ -178,6 +172,11 @@
$namakuman = '';
$namakuman1 = '';
$namakuman2 = '';
if ($periksa->kd_spesimen == 'KULTUR JMR' OR $periksa->kd_spesimen == 'JAMUR'){
$teksmikroorganisme = 'Jamur yang ditemukan : ';
} else {
$teksmikroorganisme = 'Bakteri yang ditemukan : ';
}
@endphp
@if (isset($antibiotiks) && $antibiotiks->isNotEmpty())
@php
@@ -188,16 +187,16 @@
if (isset($data['bakteri'])) {
if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK') {
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
$namakuman = $data['bakteri'];
}
}
if (isset($data['id_bakteri01']) AND $namakuman != $data['id_bakteri01']) {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
$namakuman1 = $data['id_bakteri01'];
}
if (isset($data['id_bakteri02']) AND $namakuman != $data['id_bakteri02']) {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
$namakuman2 = $data['id_bakteri02'];
}
}
@@ -210,7 +209,7 @@
@if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK')
@else
@if ($sudahcetak1 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['bakteri'] }}</i></strong> {{ $data['bakterisir'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['bakteri'] }}</i></strong> {{ $data['bakterisir'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol']))
@@ -228,7 +227,7 @@
@if (isset($data['id_bakteri01cetak']) AND $data['id_bakteri01cetak'] == 'TIDAK')
@else
@if ($sudahcetak2 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['id_bakteri01'] }}</i></strong> {{ $data['id_bakterisir01'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['id_bakteri01'] }}</i></strong> {{ $data['id_bakterisir01'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks01']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks01'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol01']))
@@ -246,7 +245,7 @@
@if (isset($data['id_bakteri02cetak']) AND $data['id_bakteri02cetak'] == 'TIDAK')
@else
@if ($sudahcetak3 == 0)
<p>Mikroorganisme yang ditemukan : <strong><i>{{ $data['id_bakteri02'] }}</i></strong> {{ $data['id_bakterisir02'] ?? '' }}</p>
<p>{{ $teksmikroorganisme }} <strong><i>{{ $data['id_bakteri02'] }}</i></strong> {{ $data['id_bakterisir02'] ?? '' }}</p>
@if (isset($data['id_bakterihitungkolteks02']))
<p>Jumlah Koloni : <strong><i>{{ $data['id_bakterihitungkolteks02'] }}</i></strong></p>
@elseif (isset($data['id_bakterihitungkol02']))
@@ -316,7 +315,7 @@
if (isset($data['bakteri']) AND $namakuman == '') {
if (isset($data['id_baktericetak']) AND $data['id_baktericetak'] == 'TIDAK') {
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['bakteri'] . "</i></strong> " . ($data['bakterisir'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol'])){
@@ -328,7 +327,7 @@
if (isset($data['id_bakteri01cetak']) AND $data['id_bakteri01cetak'] == 'TIDAK'){
} else {
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri01'] . "</i></strong> " . ($data['id_bakterisir01'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks01'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks01']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol01'])){
@@ -340,7 +339,7 @@
if (isset($data['id_bakteri02cetak']) AND $data['id_bakteri02cetak'] == 'TIDAK'){
} else{
echo "<p>Mikroorganisme yang ditemukan : <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
echo "<p>" . $teksmikroorganisme . " <strong><i>" . $data['id_bakteri02'] . "</i></strong> " . ($data['id_bakterisir02'] ?? '') . "</p>";
if (isset($data['id_bakterihitungkolteks02'])){
echo "<p>Jumlah Koloni : <strong><i>".$data['id_bakterihitungkolteks02']."</i></strong></p>";
} elseif (isset($data['id_bakterihitungkol02'])){
@@ -50,9 +50,9 @@
<td valign="top">No.Rekam Medik</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->noregister }}</td>
<td valign="top">Diagnosa</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->klinis !!}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
</tr>
<tr>
@@ -88,7 +88,7 @@
<tr>
<td width="45">&nbsp;</td>
<td valign="top" colspan="7" width="755">
@php
@php
$igg_parameter = '';
$igm_parameter = '';
$iggigm_interpretasi = '';
@@ -100,7 +100,11 @@
if (trim($rows->komponen) == 'iggigm_interpretasi') {$iggigm_interpretasi = $rows->isidata; }
}
}
if ($periksa->kd_spesimen == 'KULTUR JMR' OR $periksa->kd_spesimen == 'JAMUR'){
$teksmikroorganisme = 'Jamur yang ditemukan : ';
} else {
$teksmikroorganisme = 'Bakteri yang ditemukan : ';
}
@endphp
<table style="width:100%">
<tr>
@@ -127,7 +131,7 @@
@if (!isset($cekketerangan[1]) && isset($antibiotiks) && $antibiotiks->isNotEmpty())
@if ($bakteri != '')
<p>Mikroorganisme yang ditemukan : <strong><i>{{$bakteri}}</i></strong> {{$data['bakterisir'] ?? ''}}</p>
<p>{{$teksmikroorganisme}} <strong><i>{{$bakteri}}</i></strong> {{$data['bakterisir'] ?? ''}}</p>
@endif
@php
// Filter antibiotik yang printrow == true
@@ -0,0 +1,97 @@
@extends('base.layout')
@section('content')
<style>
table {
margin-bottom: 30px;
}
</style>
<div class="wrapper">
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<div class="card-box ribbon-box">
<div class="ribbon ribbon-primary">Mikroskopis BTA SP Laki-Laki Bulan {{$bulan}} Tahun {{$tahun}}</div>
<p></p>
<div class="table-responsive">
<button type="button" class="btn btn-primary" id="btnexport"><i class="fa fa-print"></i> Export</button>
<table cellpadding="0" cellspacing="0" id="btasptable" class="table table-striped table-bordered">
<thead>
<tr>
<th>Nilai</th>
<th>Pagi</th>
<th>Sewaktu</th>
</tr>
</thead>
<tbody>
@foreach($nilaiList as $nilai)
<tr>
<td>{{ $nilai }}</td>
<td>{{ $rekapLaki[$nilai]['pagi'] ?? 0 }}</td>
<td>{{ $rekapLaki[$nilai]['sewaktu'] ?? 0 }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="card-footer">
</div>
</div>
</div>
<div class="col-sm-6">
<div class="card-box ribbon-box">
<div class="ribbon ribbon-danger">Mikroskopis BTA SP Perempuan Bulan {{$bulan}} Tahun {{$tahun}}</div>
<p></p>
<div class="table-responsive">
<button type="button" class="btn btn-primary" id="btnexportperempuan"><i class="fa fa-print"></i> Export</button>
<table cellpadding="0" cellspacing="0" id="btasptableperempuan" class="table table-striped table-bordered">
<thead>
<tr>
<th>Nilai</th>
<th>Pagi</th>
<th>Sewaktu</th>
</tr>
</thead>
<tbody>
@foreach($nilaiList as $nilai)
<tr>
<td>{{ $nilai }}</td>
<td>{{ $rekapPerempuan[$nilai]['pagi'] ?? 0 }}</td>
<td>{{ $rekapPerempuan[$nilai]['sewaktu'] ?? 0 }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="card-footer">
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('script')
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('btnexport').addEventListener('click', function() {
var table = document.getElementById('btasptable');
if (table) {
var wb = XLSX.utils.table_to_book(table, { sheet: "Report" });
XLSX.writeFile(wb, "mikroskopisbtasplakilaki_{{$bulan}}_Tahun{{$tahun}}.xlsx");
} else {
console.error('Tabel dengan ID "btasptable" tidak ditemukan.');
}
});
document.getElementById('btnexportperempuan').addEventListener('click', function() {
var table = document.getElementById('btasptableperempuan');
if (table) {
var wb = XLSX.utils.table_to_book(table, { sheet: "Report" });
XLSX.writeFile(wb, "mikroskopisbtaspperempuan_{{$bulan}}_Tahun{{$tahun}}.xlsx");
} else {
console.error('Tabel dengan ID "btasptableperempuan" tidak ditemukan.');
}
});
});
</script>
@endpush
@@ -1,8 +1,13 @@
@extends(Session::get('previlage') == 'developer' ? 'dokter.ppdsdeveloper' : 'dokter.ppds')
@extends('dokter.ppdsdeveloper')
@push('script')
<script type="text/javascript">
$(function () {
if (window.mikroExpertiseAutoOpenInitialized) {
return;
}
window.mikroExpertiseAutoOpenInitialized = true;
var focusPeriksaId = @json((string) ($focusPeriksaId ?? ''));
var focusNoFoto = @json((string) ($focusNoFoto ?? ''));
var returnUrl = @json($expertiseReturnUrl ?? url('/'));
@@ -15,7 +20,7 @@
var maxAutoOpenAttempts = 12;
var hasOpenedTarget = false;
var hasRequestedFilteredLoad = false;
window.mikroExpertiseDirectOpen = shouldOpenOnLoad;
function setInitialFilters() {
if ($('#mulai').length) {
$('#mulai').val(initialStartDate);
@@ -47,26 +52,26 @@
}).first();
}
function triggerExpertiseButton(boundIndex) {
function triggerExpertiseButton(boundIndex, rowData) {
try {
var columns = $('#gridpemeriksaan').jqxGrid('columns');
var records = columns && columns.records ? columns.records : [];
for (var i = 0; i < records.length; i++) {
var column = records[i];
if (column && typeof column.buttonclick === 'function') {
var renderedLabel = typeof column.cellsrenderer === 'function'
? column.cellsrenderer()
: '';
var column = records[i];
if (column && typeof column.buttonclick === 'function') {
var renderedLabel = typeof column.cellsrenderer === 'function'
? column.cellsrenderer()
: '';
if (String(renderedLabel).indexOf('Expertise') !== -1) {
column.buttonclick(boundIndex);
column.buttonclick(rowData || boundIndex);
return true;
}
}
}
} catch (error) {
console.log(error);
console.error('[mikro-expertise] triggerExpertiseButton:error', error);
}
var button = findExpertiseButton(boundIndex);
@@ -85,12 +90,14 @@
var rows = $('#gridpemeriksaan').jqxGrid('getrows') || [];
var matchedRow = null;
var matchedRowIndex = -1;
var matchedRowBoundIndex = -1;
for (var i = 0; i < rows.length; i++) {
if (String(rows[i].id) === focusPeriksaId || (focusNoFoto !== '' && String(rows[i].nofoto) === focusNoFoto)) {
matchedRow = rows[i];
matchedRowIndex = i;
matchedRowBoundIndex = typeof rows[i].boundindex !== 'undefined'
? rows[i].boundindex
: i;
break;
}
}
@@ -99,16 +106,18 @@
autoOpenAttempts++;
if (autoOpenAttempts >= maxAutoOpenAttempts) {
$('#divawal').show();
} else {
setTimeout(openFocusedPeriksa, 500);
}
return;
}
var triggerRowIndex = matchedRowIndex >= 0 ? matchedRowIndex : matchedRow.boundindex;
var triggerRowIndex = matchedRowBoundIndex >= 0 ? matchedRowBoundIndex : matchedRow.boundindex;
$('#gridpemeriksaan').jqxGrid('selectrow', triggerRowIndex);
$('#gridpemeriksaan').jqxGrid('ensurerowvisible', triggerRowIndex);
setTimeout(function () {
if (triggerExpertiseButton(triggerRowIndex)) {
if (triggerExpertiseButton(triggerRowIndex, matchedRow)) {
hasOpenedTarget = true;
return;
}
@@ -116,28 +125,39 @@
autoOpenAttempts++;
if (autoOpenAttempts >= maxAutoOpenAttempts) {
$('#divawal').show();
} else {
setTimeout(openFocusedPeriksa, 500);
}
}, 150);
}
$('#gridpemeriksaan').off('bindingcomplete.mikro-expertise').on('bindingcomplete.mikro-expertise', function () {
if (!hasRequestedFilteredLoad) {
hasRequestedFilteredLoad = true;
setInitialFilters();
setTimeout(function () {
if (typeof openedpage === 'function') {
openedpage();
} else {
$('#divawal').show();
}
}, 0);
function requestFilteredLoad() {
if (hasRequestedFilteredLoad) {
return;
}
openFocusedPeriksa();
});
hasRequestedFilteredLoad = true;
setInitialFilters();
if (typeof openedpage === 'function') {
openedpage();
} else {
$('#divawal').show();
}
}
$('#btnkembali2').off('click.mikro-expertise').on('click.mikro-expertise', function () {
$('#gridpemeriksaan').off('bindingcomplete.mikro-expertise').on('bindingcomplete.mikro-expertise', function () {
openFocusedPeriksa();
});
$(window).off('load.mikro-expertise').on('load.mikro-expertise', function () {
requestFilteredLoad();
});
setTimeout(function () {
requestFilteredLoad();
}, 0);
$('#btnkembali2').off('click.mikro-expertise').on('click.mikro-expertise', function () {
window.location.href = returnUrl;
});
});
File diff suppressed because it is too large Load diff
File diff suppressed because it is too large Load diff
File diff suppressed because it is too large Load diff
@@ -0,0 +1,160 @@
<div>
<div class="bio-step-panel">
<div class="bio-step-title">Pilih Lemari</div>
<div class="bio-choice-grid">
@forelse($cabinets as $cabinet)
<button type="button"
class="bio-choice {{ (int) $selectedCabinetId === (int) $cabinet->id ? 'active' : '' }}"
wire:click="selectCabinet({{ $cabinet->id }})">
<strong>{{ $cabinet->code }} - {{ $cabinet->name }}</strong>
<span>{{ $cabinet->location ?? 'Lokasi belum diisi' }}</span>
<small>{{ $cabinet->racks_count }} rack | {{ $cabinet->specimens_count }} tube terisi</small>
</button>
@empty
<div class="alert alert-warning m-b-0">Belum ada data lemari biorepository.</div>
@endforelse
</div>
</div>
@if($selectedCabinet)
<div class="d-flex justify-content-between align-items-center m-b-15">
<h5 class="m-b-0">{{ $selectedCabinet->code }} - {{ $selectedCabinet->name }}</h5>
<form method="POST" action="{{ route('biorepository.deleteCabinet', $selectedCabinet->id) }}" class="js-delete-cabinet">
@csrf
<button type="submit" class="btn btn-sm btn-danger">Delete Lemari</button>
</form>
</div>
<div class="bio-step-panel">
<div class="bio-step-title">Pilih Rack</div>
<div class="bio-choice-grid">
@forelse($selectedCabinet->racks as $rack)
@php
$slotCount = (int) ($rack->slot_count ?: 1);
$shelfCount = (int) $rack->level;
$boxCount = (int) ($rack->box_number ?: 1);
$tubeCapacity = (int) $rack->capacity;
@endphp
<div class="bio-choice-wrap">
<button type="button"
class="bio-choice {{ (int) $selectedRackId === (int) $rack->id ? 'active' : '' }}"
wire:click="selectRack({{ $rack->id }})">
<strong>{{ $rack->code }} - {{ $rack->name }}</strong>
<span>Rack No {{ $rack->rack_number ?? $rack->id }}</span>
<small>{{ $slotCount }} slot | {{ $shelfCount }} shelf/slot | {{ $boxCount }} box/shelf | {{ $tubeCapacity }} tube/box</small>
<small>Terisi: {{ $rack->specimens_count }}</small>
</button>
<form method="POST" action="{{ route('biorepository.deleteRack', $rack->id) }}" class="js-delete-rack bio-delete-form">
@csrf
<button type="submit" class="btn btn-xs btn-danger">Delete</button>
</form>
</div>
@empty
<div class="alert alert-light m-b-0">Belum ada rack pada lemari ini.</div>
@endforelse
</div>
</div>
@endif
@if($selectedRack)
@php
$slotCount = max(1, (int) ($selectedRack->slot_count ?: 1));
$shelfCount = max(0, (int) $selectedRack->level);
$boxCount = max(1, (int) ($selectedRack->box_number ?: 1));
$tubeCapacity = max(0, (int) $selectedRack->capacity);
$tubeMap = [];
foreach ($selectedRack->specimens as $item) {
if ($item->slot_number && $item->shelf_number && $item->box_number && $item->tube_number) {
$tubeMap[$item->slot_number.'-'.$item->shelf_number.'-'.$item->box_number.'-'.$item->tube_number] = $item;
}
}
@endphp
<div class="bio-step-panel">
<div class="bio-step-title">Pilih Slot</div>
<div class="bio-number-grid">
@for($slot = 1; $slot <= $slotCount; $slot++)
<button type="button"
class="bio-number {{ (int) $selectedSlot === $slot ? 'active' : '' }}"
wire:click="selectSlot({{ $slot }})">
Slot {{ $slot }}
</button>
@endfor
</div>
</div>
@if($selectedSlot)
<div class="bio-step-panel">
<div class="bio-step-title">Pilih Shelf</div>
<div class="bio-number-grid">
@for($shelf = 1; $shelf <= $shelfCount; $shelf++)
<button type="button"
class="bio-number {{ (int) $selectedShelf === $shelf ? 'active' : '' }}"
wire:click="selectShelf({{ $shelf }})">
Shelf {{ $shelf }}
</button>
@endfor
</div>
</div>
@endif
@if($selectedSlot && $selectedShelf)
<div class="bio-step-panel">
<div class="bio-step-title">Pilih Box</div>
<div class="bio-number-grid">
@for($box = 1; $box <= $boxCount; $box++)
<button type="button"
class="bio-number {{ (int) $selectedBox === $box ? 'active' : '' }}"
wire:click="selectBox({{ $box }})">
Box {{ $box }}
</button>
@endfor
</div>
</div>
@endif
@if($selectedSlot && $selectedShelf && $selectedBox)
<div class="bio-step-panel">
<div class="bio-step-title">Tube List</div>
@if($tubeCapacity > 0)
<div class="tube-grid">
@for($tube = 1; $tube <= $tubeCapacity; $tube++)
@php $tubeKey = $selectedSlot.'-'.$selectedShelf.'-'.$selectedBox.'-'.$tube; @endphp
@if(isset($tubeMap[$tubeKey]))
<button type="button"
class="slot-btn slot-filled js-slot-filled"
data-specimen-id="{{ $tubeMap[$tubeKey]->id }}"
data-rack-id="{{ $selectedRack->id }}"
data-shelf="{{ $selectedShelf }}"
data-rackno="{{ $selectedRack->rack_number ?? $selectedRack->id }}"
data-slot="{{ $selectedSlot }}"
data-box="{{ $selectedBox }}"
data-tube="{{ $tube }}"
data-bakteri="{{ $tubeMap[$tubeKey]->bacteria_name ?? $tubeMap[$tubeKey]->specimen_name }}"
data-strain="{{ $tubeMap[$tubeKey]->strain }}"
data-volume="{{ $tubeMap[$tubeKey]->volume ?? 0 }}"
title="{{ $tubeMap[$tubeKey]->specimen_code }} - {{ $tubeMap[$tubeKey]->bacteria_name ?? $tubeMap[$tubeKey]->specimen_name }} / {{ $tubeMap[$tubeKey]->strain }}">
<span>{{ $tube }}</span>
<small>{{ $tubeMap[$tubeKey]->volume ?? 0 }} ml</small>
</button>
@else
<button type="button"
class="slot-btn js-slot"
data-rack-id="{{ $selectedRack->id }}"
data-shelf="{{ $selectedShelf }}"
data-rackno="{{ $selectedRack->rack_number ?? $selectedRack->id }}"
data-slot="{{ $selectedSlot }}"
data-box="{{ $selectedBox }}"
data-tube="{{ $tube }}">
{{ $tube }}
</button>
@endif
@endfor
</div>
@else
<div class="alert alert-light m-b-0">Konfigurasi tube belum lengkap.</div>
@endif
</div>
@endif
@endif
</div>
+21 -1
View File
@@ -2,6 +2,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MobileApiController;
/*
|--------------------------------------------------------------------------
@@ -17,8 +18,27 @@ use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Route::prefix('mobile')->group(function () {
Route::get('ping', [MobileApiController::class, 'ping']);
Route::post('login', [MobileApiController::class, 'login']);
Route::get('dashboard', [MobileApiController::class, 'dashboard']);
Route::get('critical-values', [MobileApiController::class, 'criticalValueNotifications']);
Route::post('critical-values/{id}/mark-read', [MobileApiController::class, 'criticalValueNotificationMarkRead']);
Route::get('early-warning', [MobileApiController::class, 'earlyWarning']);
Route::get('examinations/search', [MobileApiController::class, 'searchExamination']);
Route::get('initial-work/samples', [MobileApiController::class, 'initialWorkSamples']);
Route::post('initial-work/samples/{id}/accept', [MobileApiController::class, 'initialWorkAccept']);
Route::post('initial-work/samples/{id}/reject', [MobileApiController::class, 'initialWorkReject']);
Route::get('books/{master}/examinations', [MobileApiController::class, 'examinations']);
Route::get('examinations/{id}', [MobileApiController::class, 'examination']);
Route::get('examinations/{id}/expertise', [MobileApiController::class, 'expertise']);
Route::post('examinations/{id}/expertise/template', [MobileApiController::class, 'setExpertiseTemplate']);
Route::post('examinations/{id}/expertise', [MobileApiController::class, 'saveExpertise']);
});
Route::group([
'middleware' => 'auth:api'
], function () {
});
});
+11 -3
View File
@@ -13,6 +13,7 @@ use App\Http\Controllers\LogbookController;
use App\Http\Controllers\JsonTransferController;
use App\Http\Controllers\BiorepositoryController;
use App\Http\Controllers\GudangController;
use App\Http\Controllers\MobileApiController;
Route::get('/', [FrontpageController::class, 'index']);
@@ -24,7 +25,10 @@ Route::get('logout', [AuthController::class, 'logout'])->name('logout');
Route::get('hasil/{id}', [DokterController::class, 'ctkFormhasil']);
Route::get('label/{id}', [DokterController::class, 'ctkLabel']);
Route::get('cekandroid/{firebaseid}', [AuthController::class, 'cekandroid']);
Route::get('mobile/expertise/{periksa}', [MobileApiController::class, 'openExpertise']);
Route::post('registerpasien', [FrontpageController::class, 'registerApi']);
Route::post('registerulang', [FrontpageController::class, 'registerApiUlang']);
Route::post('registerjson', [FrontpageController::class, 'registerApiFormatJSON']);
Route::post('prosesbatal', [DokterController::class, 'cancelOrder']);
Route::post('ekstrakhasil', [DokterController::class, 'gethasiltoSIMRS'])->name('gethasiltoSIMRS');
@@ -38,6 +42,7 @@ Route::group(['middleware' => 'project.ipg'], function() {
Route::get('mikro/{id}', [FrontpageController::class, 'viewMikro']);
Route::get('/autocomplete-klinik', [FrontpageController::class, 'autocompleteKlinik'])->name('autocomplete.klinik');
Route::get('/autocomplete-dokter', [FrontpageController::class, 'autocompleteDokter'])->name('autocomplete.dokter');
Route::get('hasildraft/{id}', [DokterController::class, 'previewFormhasil']);
Route::get('tabsmode/{id}', [FrontpageController::class, 'viewTabletsMode']);
Route::get('penerimaansample', [FrontpageController::class, 'viewPenerimaanSample']);
@@ -78,6 +83,9 @@ Route::group(['middleware' => 'project.ipg'], function() {
Route::post('biorepository/delete-cabinet/{id}', [BiorepositoryController::class, 'deleteCabinet'])->name('biorepository.deleteCabinet');
Route::post('simbhp/exaddbarang', [GudangController::class, 'exAddbarang'])->name('exAddBarang');
Route::get('simbhp/snapshot', [GudangController::class, 'jsonSnapshot'])->name('simbhp.snapshot');
Route::get('simbhp/expired-batches', [GudangController::class, 'jsonExpiredBatches'])->name('simbhp.expiredBatches');
Route::post('simbhp/racikan', [GudangController::class, 'storeRacikan'])->name('simbhp.racikan');
Route::post('simbhp/update-expired', [GudangController::class, 'updateExpiredBatch'])->name('simbhp.updateExpired');
Route::post('simbhp/reportbhp', [GudangController::class, 'jsonReportbhp'])->name('reportBHP');
Route::get('simbhp/reportbhp/export', [GudangController::class, 'exportReportbhp'])->name('reportBHPExport');
Route::post('simbhp/kwitansi', [GudangController::class, 'exKwitansi'])->name('kwitansiBHP');
@@ -88,8 +96,8 @@ Route::group(['middleware' => 'project.ipg'], function() {
Route::post('dashboard/early-warning/{id}/cancel', [FrontpageController::class, 'cancelEarlyWarning'])->name('dashboard.earlyWarning.cancel');
Route::get('dokter/getlisttem', [DokterController::class, 'getListtem'])->name('getListTemplate');
Route::get('dokter/getlistppds', [DokterController::class, 'getListppds'])->name('getlistppds');
Route::post('dokter/getadendum', [DokterController::class, 'getAdendum'])->name('getadendum');
Route::post('dokter/store', [DokterController::class, 'store']);
Route::post('dokter/getadendum', [DokterController::class, 'getAdendum'])->name('getadendum');
Route::post('dokter/store', [DokterController::class, 'store']);
Route::post('dokter/getdokter', [DokterController::class, 'getDokter'])->name('getDokter');
Route::post('dokter/update', [DokterController::class, 'update']);
Route::post('dokter/delete', [DokterController::class, 'delete'])->name('deleteDokter');
@@ -112,7 +120,6 @@ Route::group(['middleware' => 'project.ipg'], function() {
Route::post('dokter/exprintform', [DokterController::class, 'exprintForm'])->name('exprintform');
Route::post('dokter/exprintlabelsimrs', [DokterController::class, 'exprintLabelSIMRS'])->name('exprintlabelSIMRS');
Route::post('dokter/exprintformsimrs', [DokterController::class, 'exprintFormSIMRS'])->name('exprintformSIMRS');
Route::post('dokter/exsendtomany', [DokterController::class, 'exsendTomany'])->name('exsendtomany');
Route::post('dokter/exsendtoinsitu', [DokterController::class, 'exsendToinsitu'])->name('exsendtoinsitu');
Route::post('dokter/getListadendum', [DokterController::class, 'getListadendum'])->name('getListadendum');
Route::post('dokter/exprinttandaterima', [DokterController::class, 'exTandaTerima'])->name('exTandaTerima');
@@ -146,6 +153,7 @@ Route::group(['middleware' => 'project.ipg'], function() {
Route::get('rekapantibiotik', [ReportController::class, 'genRekapAntibiotik']);
Route::get('glassreport', [ReportController::class, 'genGlassReport']);
Route::get('znreport', [ReportController::class, 'genZNreport']);
Route::get('mikroskopisbtasp', [ReportController::class, 'genMikroskopisBtaSpReport']);
Route::get('tatreport', [ReportController::class, 'genTATreport']);
Route::get('reportpenerimaansample', [ReportController::class, 'genRekapPenerimaanSample']);
Route::post('reportpenerimaansample/mark-printed', [ReportController::class, 'markRekapPenerimaanSamplePrinted'])->name('markRekapPenerimaanSamplePrinted');
+596 -551
View File
File diff suppressed because it is too large Load diff
+67 -33
View File
@@ -135,43 +135,45 @@ TARGET_MAPPING = {
# Kanan: 'Host Test Code' dari Dokumen Word Anda
GENEXPERT_TEST_MAPPING = {
# Mapping untuk IP 10.10.120.75 (Multi-Assay)
"HIV": "HIV1-VL", # Xpert HIV-1 Viral Load XC Version 3
"TCM TB": "MTBRIF", # Xpert MTBRIF Assay G4 Version 6
"TCM TB ULTRA": "MTBRIF", # Xpert MTBRIF Ultra Version 4
"TCM TB XDR": "MTB-XDR", # Xpert MTB-XDR Version 1
"HCV VL": "HCV", # Xpert HCV Viral Load Version 1
"COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2
"17.3.1 TCM COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2
"17.3.2 PCR COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2
"HIV": "HIV1-VL",
"HBV": "HBVVL",
"TCM TB": "MTBRIF",
"TCM TB ULTRA": "MTBRIF",
"TCM TB XDR": "MTB-XDR 2",
"HCV VL": "HCV",
"COVID-19": "SARSCOV2FLURSV",
"17.3.1 TCM COVID-19": "SARSCOV2FLURSV",
"17.3.2 PCR COVID-19": "SARSCOV2FLURSV",
"E.2.5 HCV TCM": "HCV",
"18.1.1 TCM HCV": "HCV",
"18.1.2 TCM HIV VIRAL LOAD": "HIV1-VL",
"18.1.4 TCM HPV": "HCV",
"18.1.4 TCM HPV": "HPV",
"7.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
"5.3.8 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
"5.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"3.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL) ": "MTB-XDR",
"2.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"1.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"1.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
"15.2.1 KULTUR TB MEDIA LJ": "MTB-XDR",
"8.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
"9.3.5 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"9.3.6 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
"12.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
"H.2.5 PEMERIKSAAN KULTUR MYCROBACTERIUM TBC": "MTB-XDR",
"12.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"11.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"10.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
"10.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"3.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
"15.2.2 KULTUR TB MEDIA MGIT (AUTOMATIC)": "MTB-XDR",
"11.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
"8.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"7.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
"5.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"3.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL) ": "MTB-XDR 2",
"2.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"1.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"1.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
"15.2.1 KULTUR TB MEDIA LJ": "MTB-XDR 2",
"8.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
"9.3.5 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"9.3.6 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
"12.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
"H.2.5 PEMERIKSAAN KULTUR MYCROBACTERIUM TBC": "MTB-XDR 2",
"12.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"11.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"10.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
"10.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"3.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
"15.2.2 KULTUR TB MEDIA MGIT (AUTOMATIC)": "MTB-XDR 2",
"11.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
"8.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"7.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
"2.3.10 TCM CLAMIDIA TRACHOMATIS / NEISSERIA GONORRHOE": "MTBRIF",
"12.3.8 TCM TB (GENE EXPERT)": "MTBRIF",
"15.2.3 TCM TB (GENE EXPERT)": "MTBRIF",
"15.2.3 TCM TB (GENE EXPERT) GENE EXPERT": "MTBRIF",
"2.3.9 TCM GENE EXPERT": "MTBRIF",
"3.3.8 TCM GENE EXPERT": "MTBRIF",
"3.3.9 TCM MYCOBACTERIUM TUBERCULOSIS": "MTBRIF",
@@ -183,11 +185,10 @@ GENEXPERT_TEST_MAPPING = {
}
GENEXPERT_IP_CAPABILITIES = {
"10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR", "HCV VL", "SARSCOV2FLURSV"],
"10.10.120.108": ["HCV", "HIV", "HBV"],
"10.10.120.73": ["MTBRIF"]
"10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR 2", "HCV", "SARSCOV2FLURSV"],
"10.10.120.73": ["MTBRIF", "HBVVL", "HIV1-VL", "HCV", "SARSCOV2FLURSV"],
"10.10.120.108": ["MTBRIF", "HCV", "HIV1-VL", "HBVVL", "SARSCOV2FLURSV"],
}
# Default code jika nama tes di database tidak dikenali
DEFAULT_GXP_CODE = "MTBRIF"
DEVICE_CONFIGS = [
@@ -380,6 +381,28 @@ def parse_genexpert_astm_query(message_text):
query["query_sample_id"] = fields[2] if len(fields) > 2 else ""
return query
def summarize_genexpert_astm_orders(records):
summaries = []
for rec in records:
if not rec.startswith("O|"):
continue
fields = rec.split("|")
sample_id = fields[2].strip() if len(fields) > 2 else ""
container_id = fields[3].strip() if len(fields) > 3 else ""
assay_code = ""
if len(fields) > 4:
assay_parts = [part.strip() for part in fields[4].split("^") if part.strip()]
assay_code = assay_parts[-1] if assay_parts else fields[4].strip()
priority = fields[5].strip() if len(fields) > 5 else ""
action_code = fields[11].strip() if len(fields) > 11 else ""
summaries.append(
f"sample_id={sample_id or '-'}, container_id={container_id or '-'}, "
f"assay={assay_code or '-'}, priority={priority or '-'}, action={action_code or '-'}"
)
return summaries
def detect_genexpert_message_framing(message_bytes):
raw = message_bytes or b""
if b"\x1c" in raw or raw.startswith(b"\x0b"):
@@ -2570,6 +2593,17 @@ def process_genexpert_hl7_message(conn, ip_addr, clean_hl7, response_framing):
print(f"[GENEXPERT-ASTM] Menerima Heartbeat / Sesi Kosong dari alat.")
return
# E. GeneXpert dapat mengirim status/echo order ASTM berisi H/P/O/L.
# Ini bukan hasil lab karena tidak ada R record, dan bukan query karena tidak ada Q record.
if "O" in record_types and set(record_types).issubset({'H', 'P', 'O', 'L'}):
order_summaries = summarize_genexpert_astm_orders(records)
print(
f"[GENEXPERT-ASTM] Menerima status/echo order tanpa hasil dari {ip_addr}. "
f"orders={order_summaries}"
)
return
log_genexpert_hl7("GENEXPERT-ASTM", ip_addr, clean_hl7, label="pesantidakdikenali")
print(f"[GENEXPERT-ASTM] Pesan ASTM tidak dikenali dari {ip_addr}. Isi: {clean_hl7[:50]}")
return
+45
View File
@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
+45
View File
@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "ad70ec4617166f1c38e5d2bfd388af71fda14f06"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: android
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: ios
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: linux
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: macos
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: web
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
- platform: windows
create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+224
View File
@@ -0,0 +1,224 @@
# Mikrobiology Laboratory Information System (MyLIS)
MyLIS Flutter adalah aplikasi pendamping untuk Laravel LIS lokal milik Rumah Sakit Umum Daerah Dr. Saiful Anwar. Aplikasi ini fokus pada akses mobile/desktop untuk login, dashboard Early Warning Sistem, daftar pemeriksaan, detail pemeriksaan, dan pengisian expertise per template DLP.
Nama panjang aplikasi:
```text
Mikrobiology Laboratory Information System (MyLIS)
```
Pemilik aplikasi:
```text
Rumah Sakit Umum Daerah Dr. Saiful Anwar
```
Default URL Laravel lokal:
```text
https://lis.swandhana.test/
```
URL tersebut bisa diganti dari halaman sebelum login. Tersedia tombol `Ping` agar pengguna dapat memastikan server Laravel sudah dapat diakses sebelum login.
## Fitur Utama
- Login tanpa register.
- Pengaturan URL Laravel dari layar login.
- Ping koneksi ke Laravel.
- Dashboard dengan menu utama `Early Warning Sistem`.
- Daftar pemeriksaan per buku/kategori.
- Detail pemeriksaan dengan status dan tombol `Expertise`.
- Screen expertise Flutter native, tidak membuka Blade Laravel.
- Pemilihan template saat `periksa.dlp` masih kosong.
- Penyimpanan draft, preliminary, final, dan kirim ke SPV sesuai role pengguna.
- Dukungan build Android, iOS, dan macOS.
## Alur Expertise
Jika `periksa.dlp` kosong, aplikasi menampilkan pilihan template. Jika sudah terisi, aplikasi langsung membuka wizard sesuai DLP.
Screen DLP yang sudah dibuat:
- `CCI`
- `Kultur`
- `Pewarna Langsung`
- `TBC`
- `Viral Load`
- `IgM IgG Leptospira`
- `PCR COVID`
Semua wizard memakai pola umum:
1. Data pasien dan petugas.
2. Isian parameter/template sesuai DLP.
3. Tes Kepekaan Antibiotik jika berlaku.
4. Data alat jika berlaku.
5. Isian expertise akhir.
Navigasi wizard tidak memakai validasi wajib, sehingga pengguna bebas `Next` dan `Previous`.
## Role dan Tombol Simpan
Untuk role supervisor/SPV:
- `Save as Draft`
- `Save Preliminary result`
- `Save Final Result`
Untuk role selain supervisor/SPV:
- `Save as Draft`
- `Save and Send To SPV`
- `Kirim SPV Preliminary`
Checkbox `Nilai Kritis` tersedia di step akhir dan dikirim ke API saat menyimpan.
## Struktur Folder
```text
lib/
app/
app.dart
data/
expertise_data.dart
providers/
session_provider.dart
screens/
dashboard/
examinations/
expertise/
login/
services/
api_client.dart
session_store.dart
widgets/
common_widgets.dart
main.dart
```
Ringkasan:
- `main.dart`: entry point aplikasi.
- `app/app.dart`: root aplikasi dan registrasi part screen.
- `services/api_client.dart`: komunikasi HTTP ke Laravel.
- `services/session_store.dart`: penyimpanan token dan base URL.
- `providers/session_provider.dart`: pengecekan session awal.
- `screens/login`: halaman login, URL Laravel, dan ping.
- `screens/dashboard`: dashboard dan Early Warning Sistem.
- `screens/examinations`: daftar dan detail pemeriksaan.
- `screens/expertise`: semua screen wizard DLP.
- `widgets/common_widgets.dart`: komponen UI umum.
- `data/expertise_data.dart`: daftar DLP dan label template.
## API Laravel
Aplikasi memakai endpoint mobile Laravel dengan prefix:
```text
/api/mobile
```
Endpoint utama:
- `GET /api/mobile/ping`
- `POST /api/mobile/login`
- `GET /api/mobile/dashboard`
- `GET /api/mobile/early-warning`
- `GET /api/mobile/books/{master}/examinations`
- `GET /api/mobile/examinations/{id}`
- `GET /api/mobile/examinations/{id}/expertise`
- `POST /api/mobile/examinations/{id}/expertise/template`
- `POST /api/mobile/examinations/{id}/expertise`
Controller Laravel berada di:
```text
/Users/duidev/htdocs/lis/htdocs/app/Http/Controllers/MobileApiController.php
```
Route API berada di:
```text
/Users/duidev/htdocs/lis/htdocs/routes/api.php
```
## Menjalankan Aplikasi
Ambil dependency:
```bash
flutter pub get
```
Jalankan di device/simulator:
```bash
flutter run
```
Jalankan dengan default URL lain:
```bash
flutter run --dart-define=LIS_BASE_URL=https://lis.swandhana.test/
```
## Verifikasi
Analisis kode Flutter:
```bash
flutter analyze
```
Test Flutter:
```bash
flutter test
```
Cek route Laravel mobile:
```bash
php artisan route:list --path=mobile
```
Cek sintaks controller Laravel:
```bash
php -l app/Http/Controllers/MobileApiController.php
```
## Build
Android debug:
```bash
flutter build apk --debug
```
iOS debug tanpa code signing:
```bash
flutter build ios --debug --no-codesign
```
macOS debug:
```bash
flutter build macos --debug
```
Output build yang umum:
- Android: `build/app/outputs/flutter-apk/app-debug.apk`
- iOS: `build/ios/iphoneos/Runner.app`
- macOS: `build/macos/Build/Products/Debug/mylis.app`
## Catatan Lokal
- Pastikan Laravel lokal `https://lis.swandhana.test/` aktif.
- Pastikan sertifikat lokal dipercaya oleh device/simulator yang dipakai.
- Untuk iOS device fisik, build `--no-codesign` tetap perlu signing manual sebelum deploy.
- Jangan menjalankan beberapa command Flutter berat secara bersamaan karena bisa berebut file sementara pada folder platform.
+28
View File
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
+14
View File
@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
+64
View File
@@ -0,0 +1,64 @@
import java.util.Properties
plugins {
id("com.android.application")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
val keystoreProperties =
Properties().apply {
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystorePropertiesFile.inputStream().use { load(it) }
}
}
android {
namespace = "com.duidev.mylis"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.duidev.mylis"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = (keystoreProperties["storeFile"] as String?)?.let {
file(it)
}
storePassword = keystoreProperties["storePassword"] as String?
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
flutter {
source = "../.."
}
+29
View File
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "732589737888",
"project_id": "mylis-a4983",
"storage_bucket": "mylis-a4983.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:732589737888:android:3f782e07e62ace55a446c5",
"android_client_info": {
"package_name": "com.duidev.mylis"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAibwJgxVnkVOOIGsjbf2SpW_isJOFTtAg"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:label="MyLIS"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -0,0 +1,5 @@
package com.duidev.mylis
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
+24
View File
@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
# This newDsl flag was added by the Flutter template
android.newDsl=false
# This builtInKotlin flag was added by the Flutter template
android.builtInKotlin=false
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
+26
View File
@@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "9.0.1" apply false
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
}
include(":app")
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

+34
View File
@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
+1
View File
@@ -0,0 +1 @@
#include "Generated.xcconfig"
+1
View File
@@ -0,0 +1 @@
#include "Generated.xcconfig"
+647
View File
@@ -0,0 +1,647 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = R8C84SJUAF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.duidev.mylis;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.duidev.mylis.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.duidev.mylis.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.duidev.mylis.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = R8C84SJUAF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.duidev.mylis;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = R8C84SJUAF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.duidev.mylis;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
+16
View File
@@ -0,0 +1,16 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "[email protected]",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Loaded 100 of 217 files, more files were not shown because too many files have changed in this diff. Show more