Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97de187bc8 | ||
|
|
5b284fd0d3 | ||
|
|
6c6520b3eb | ||
|
|
59d448360f | ||
|
|
3decbec88e | ||
|
|
c3af859439 | ||
|
|
ec2c3178ee | ||
|
|
fd3068bead | ||
|
|
550fc64763 | ||
|
|
58e27eb92b | ||
|
|
765bfd91f3 | ||
|
|
36582a9811 | ||
|
|
d764663f35 | ||
|
|
14301f7216 | ||
|
|
9dc9bccc76 | ||
|
|
36ad5d7af2 | ||
|
|
a04c5330d5 | ||
|
|
3ef25c9e95 | ||
|
|
b82a799e20 | ||
|
|
f0dc7987a9 | ||
|
|
73a9ac103e | ||
|
|
3e6b70d7bd | ||
|
|
136470d4ba | ||
|
|
9363533c3c | ||
|
|
b93784be6a | ||
|
|
caa039b5b4 | ||
|
|
d8fc98d1a8 | ||
|
|
fd865f6756 | ||
|
|
587267b63a | ||
|
|
08a62487ab | ||
|
|
bb2c893872 | ||
|
|
3192425fc6 | ||
|
|
c2cbdff4e5 | ||
|
|
d369d3754b | ||
|
|
2bf4cac343 | ||
|
|
207c9ef17d | ||
|
|
f97105aeb0 | ||
|
|
60681f08cc | ||
|
|
b957be735d | ||
|
|
3e1ed72727 | ||
|
|
334728e16e | ||
|
|
16f541ca77 | ||
|
|
cd07dee705 | ||
|
|
c7771029ee | ||
|
|
19c3e0b6d4 | ||
|
|
d5f6f2812c | ||
|
|
4ebcbf0869 | ||
|
|
8e87beeb9f | ||
|
|
2f60f36307 | ||
|
|
03855a99b1 | ||
|
|
0891978d46 | ||
|
|
a3a98b13e3 | ||
|
|
119f73403e | ||
|
|
410916f963 | ||
|
|
d1e6ab0133 | ||
|
|
73b2e1ca7c | ||
|
|
9cc78b23e0 | ||
|
|
213270ede1 | ||
|
|
e04d43dfdf | ||
|
|
848f0e4100 | ||
|
|
5774d55f71 | ||
|
|
9e4774bd5e | ||
|
|
92dc35396b | ||
|
|
040284ec29 | ||
|
|
b222a79283 | ||
|
|
32db765b98 | ||
|
|
6d0f9a8bf6 | ||
|
|
d944647129 | ||
|
|
707e2cb841 | ||
|
|
0123036226 | ||
|
|
08984801f9 | ||
|
|
82dc63822e | ||
|
|
d1e9fe03d6 | ||
|
|
8a233d6c1b | ||
|
|
335355a5ba | ||
|
|
4450076726 | ||
|
|
5e097c1abc | ||
|
|
92117e3320 | ||
|
|
3f0f6534d7 | ||
|
|
7febb03b5d | ||
|
|
30e39875c0 | ||
|
|
616f0658d6 | ||
|
|
9b831791f5 | ||
|
|
79ff65d87c | ||
|
|
d6bba3e216 | ||
|
|
943282e3f9 | ||
|
|
0f93db0431 | ||
|
|
f2dbbf9f5e | ||
|
|
741ac2dffd | ||
|
|
510e8a2352 | ||
|
|
146bab0a46 | ||
|
|
d725332bc3 | ||
|
|
67ff560077 | ||
|
|
0f8c73bc50 | ||
|
|
f857eec85c | ||
|
|
ed92325536 | ||
|
|
9a65fa8227 | ||
|
|
480db23635 | ||
|
|
7758df12f2 | ||
|
|
cc11aecb10 |
No files matched your search
@@ -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.
|
||||
@@ -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=
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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'
|
||||
];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
};
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"> </td>
|
||||
<td valign="top"> </td>
|
||||
<td valign="top" colspan="2"> </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"> </td>
|
||||
<td valign="top"> </td>
|
||||
<td valign="top" colspan="2"> </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"> </td>
|
||||
<td valign="top"> </td>
|
||||
<td valign="top" colspan="2"> </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"> </td>
|
||||
<td valign="top"> </td>
|
||||
<td valign="top" colspan="2"> </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"> </td>
|
||||
<td valign="top"> </td>
|
||||
<td valign="top" colspan="2"> </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"> </td>
|
||||
<td valign="top"> </td>
|
||||
<td valign="top" colspan="2"> </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -100,12 +100,6 @@
|
||||
<td valign="top" colspan="2"> </td>
|
||||
</tr>
|
||||
<tr><td colspan="8"> </td></tr>
|
||||
<tr>
|
||||
<td width="45"> </td>
|
||||
<td valign="top" colspan="7" width="755">
|
||||
<h1>HASIL PEMERIKSAAN LABORATORIUM MIKROBIOLOGI KLINIK</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="45"> </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"> </td>
|
||||
<td valign="top"> </td>
|
||||
<td valign="top" colspan="2"> </td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -88,7 +88,7 @@
|
||||
<tr>
|
||||
<td width="45"> </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;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
@@ -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 () {
|
||||
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 = "../.."
|
||||
}
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
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>
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 67 KiB |
@@ -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
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -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 "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||
<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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 1.7 KiB |