395 lines
18 KiB
Markdown
395 lines
18 KiB
Markdown
# ✅ QMD — Quality Management Document
|
|
|
|
**Project:** Web Antrean — Sistem Manajemen Antrean Rawat Jalan RSSA
|
|
**Version:** 1.0.0
|
|
**Author:** Akbar
|
|
**Stack:** JavaScript · Vue 3 · TypeScript · Nuxt 3 · Vuetify 3 · Pinia
|
|
**Last Updated:** 2026-05-25
|
|
|
|
---
|
|
|
|
## 🧠 AI Prompt — Cara Menggunakan Dokumen Ini
|
|
|
|
> Salin prompt berikut ke Claude untuk membantu QA planning:
|
|
|
|
```
|
|
Kamu adalah QA engineer senior untuk project Nuxt 3 + Vue 3 + TypeScript.
|
|
Project: Web Antrean — Sistem manajemen antrean rawat jalan RSSA.
|
|
|
|
Fitur utama:
|
|
- Anjungan mandiri (kiosk registrasi pasien, pilih klinik/subspesialis)
|
|
- Check-in pasien via QR code
|
|
- Manajemen antrean loket (panggil, skip, recall, selesai)
|
|
- Manajemen antrean klinik & penunjang
|
|
- Real-time sync via WebSocket + polling fallback
|
|
- Dashboard monitoring & statistik
|
|
- Setting: master data (klinik, loket, penunjang, screen), hak akses (Keycloak role/group)
|
|
- Cetak tiket via thermal printer
|
|
|
|
Bantu aku membuat:
|
|
1. Test plan lengkap (unit, integration, E2E)
|
|
2. Test cases untuk fitur di atas
|
|
3. Definition of Done (DoD) per story
|
|
4. Checklist code review untuk Vue 3 + TypeScript
|
|
5. Standar kualitas kode (naming, linting, typing)
|
|
|
|
Format dalam tabel Markdown. Tool: Vitest, Cypress, Vue Test Utils, happy-dom.
|
|
```
|
|
|
|
---
|
|
|
|
## 1. Quality Objectives
|
|
|
|
| Objektif | Target | Cara Ukur |
|
|
|----------|--------|-----------|
|
|
| Test Coverage (Unit) | > 60% | Vitest coverage report (`npx vitest run --coverage`) |
|
|
| Bug Rate (prod) | < 5 bug/sprint | Manual tracking / issue log |
|
|
| Code Review | 100% PR di-review | Git workflow |
|
|
| TypeScript strict | Minimal `any` type | ESLint + `tsc --noEmit` |
|
|
| WebSocket Reliability | > 99% message delivery | Monitoring log + polling fallback |
|
|
| Page Load | < 3 detik | Lighthouse / manual timing di jaringan LAN |
|
|
|
|
---
|
|
|
|
## 2. Testing Strategy
|
|
|
|
### 2.1 Piramida Testing
|
|
```
|
|
▲
|
|
/E2E\ ← Cypress (browser real)
|
|
/──────\
|
|
/ Integ \ ← Vitest + Nuxt Test Utils
|
|
/──────────\
|
|
/ Unit Test \ ← Vitest + Vue Test Utils + happy-dom
|
|
/______________\
|
|
```
|
|
|
|
### 2.2 Test Toolchain
|
|
|
|
| Tipe | Tool | Config File | Status |
|
|
|------|------|-------------|--------|
|
|
| Unit | Vitest + happy-dom | `vitest.config.ts` | ✅ Terkonfigurasi |
|
|
| Component | Vue Test Utils (`@vue/test-utils`) | — | ✅ Terinstal |
|
|
| E2E | Cypress | `cypress.config.ts` | ✅ Terkonfigurasi |
|
|
| Component (Cypress) | Cypress Component Testing | `cypress.config.ts` → `component` | ✅ Terkonfigurasi |
|
|
| Linting | ESLint (Nuxt preset) | `eslint.config.mjs` | ✅ Terkonfigurasi |
|
|
| Type Check | TypeScript (via Nuxt) | `tsconfig.json` → extends `.nuxt/tsconfig.json` | ✅ |
|
|
| Test Environment | happy-dom | `vitest.config.ts` → `environment: 'happy-dom'` | ✅ |
|
|
|
|
### 2.3 Test Commands
|
|
|
|
```bash
|
|
# Unit & Component Tests
|
|
npm run test # vitest (watch mode)
|
|
npm run test:ui # vitest --ui (browser UI)
|
|
|
|
# E2E Tests
|
|
npm run cypress:open # Cypress interactive
|
|
npm run cypress:run # Cypress headless
|
|
|
|
# Linting
|
|
npx eslint .
|
|
|
|
# Type Check
|
|
npx nuxi typecheck
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Test Cases
|
|
|
|
### 3.1 Unit Tests — Composables
|
|
|
|
| ID | Modul | Test Case | Expected | Status |
|
|
|----|-------|-----------|----------|--------|
|
|
| UT-001 | `useAuth.ts` | Login — mengembalikan user data setelah Keycloak auth | `user.value` tidak null, memiliki `id`, `roles`, `groups` | `Todo` |
|
|
| UT-002 | `useAuth.ts` | Logout — session di-clear dan redirect ke login page | `user.value` menjadi null, navigasi ke `/LoginPage` | `Todo` |
|
|
| UT-003 | `useQueue.js` | `processNextQueue()` — mengambil pasien berikutnya sesuai loketId | Return pasien dengan status `menunggu` dan `loketId` yang cocok | `Todo` |
|
|
| UT-004 | `useQueue.js` | `processNextQueue()` — skip pasien dari loket lain | Pasien dari loket lain tidak terproses | `Todo` |
|
|
| UT-005 | `useWebSocket.ts` | Koneksi sukses — state connected | `isConnected.value === true` setelah open event | `Todo` |
|
|
| UT-006 | `useWebSocket.ts` | Auto-reconnect setelah disconnect | Reconnect attempt dalam < 5 detik | `Todo` |
|
|
| UT-007 | `useCheckIn.ts` | Check-in valid QR — update status pasien | Status pasien berubah ke `hadir`, return success | `Todo` |
|
|
| UT-008 | `useCheckIn.ts` | Check-in invalid QR — error handling | Return error message, status tidak berubah | `Todo` |
|
|
| UT-009 | `useQRScanner.ts` | Inisialisasi scanner — kamera aktif | Scanner instance terbuat tanpa error | `Todo` |
|
|
| UT-010 | `useHakAkses.ts` | Fetch permissions — mapping role ke menu | Menu permissions sesuai dengan role & group user | `Todo` |
|
|
| UT-011 | `useThermalPrint.ts` | Generate tiket — format nomor antrean benar | Output mengandung kode klinik + nomor urut | `Todo` |
|
|
| UT-012 | `useClinicAPI.ts` | Fetch daftar klinik — return data klinik aktif | Array klinik tidak kosong, setiap item punya `id` & `nama` | `Todo` |
|
|
|
|
### 3.2 Unit Tests — Stores (Pinia)
|
|
|
|
| ID | Modul | Test Case | Expected | Status |
|
|
|----|-------|-----------|----------|--------|
|
|
| UT-013 | `queueStore.js` | `allPatients` — menyimpan & mengembalikan daftar pasien | State `allPatients` terisi array setelah fetch | `Todo` |
|
|
| UT-014 | `queueStore.js` | `currentProcessingPatient` — isolasi per loket | Setiap loket punya key unik di persisted state | `Todo` |
|
|
| UT-015 | `clinicStore.js` | Fetch daftar klinik dari API | `clinics` terisi data dari klinik-api | `Todo` |
|
|
| UT-016 | `doctorStore.js` | Blacklist endpoint gagal | Endpoint yang 500 di-blacklist, tidak di-retry spam | `Todo` |
|
|
| UT-017 | `loketStore.js` | State loket terisolasi antar loket | Operasi di loket A tidak mempengaruhi loket B | `Todo` |
|
|
| UT-018 | `masterStore.js` | CRUD master klinik | Create, read, update, delete berjalan tanpa error | `Todo` |
|
|
| UT-019 | `permissionStore.ts` | Load permission sesuai role | Permission loaded dan accessible via getter | `Todo` |
|
|
|
|
### 3.3 Unit Tests — Middleware
|
|
|
|
| ID | Modul | Test Case | Expected | Status |
|
|
|----|-------|-----------|----------|--------|
|
|
| UT-020 | `auth.ts` | User belum login → redirect ke `/LoginPage` | `navigateTo('/LoginPage')` dipanggil | `Todo` |
|
|
| UT-021 | `auth.ts` | User sudah login → lanjut ke halaman tujuan | Tidak ada redirect | `Todo` |
|
|
| UT-022 | `guest.ts` | User sudah login akses `/LoginPage` → redirect ke `/dashboard` | `navigateTo('/dashboard')` dipanggil | `Todo` |
|
|
| UT-023 | `permissions.ts` | User tanpa akses ke halaman → redirect/block | Akses ditolak, redirect ke halaman authorized | `Todo` |
|
|
| UT-024 | `checkPageAccess.ts` | Validasi hak akses per halaman berdasarkan group | Halaman hanya bisa diakses sesuai permission | `Todo` |
|
|
|
|
### 3.4 Component Tests
|
|
|
|
| ID | Komponen | Skenario | Expected | Status |
|
|
|----|----------|----------|----------|--------|
|
|
| CT-001 | `PatientCard.vue` | Render data pasien lengkap | Nama, noRM, nomor antrean, status, subspesialis tampil | `Todo` |
|
|
| CT-002 | `PatientCard.vue` | Status badge warna sesuai status | `menunggu` = kuning, `dipanggil` = biru, `selesai` = hijau | `Todo` |
|
|
| CT-003 | `CurrentPatientCard.vue` | Tampilkan pasien yang sedang diproses | Data pasien aktif tampil dengan aksi (selesai, skip) | `Todo` |
|
|
| CT-004 | `QueueActionsCard.vue` | Tombol aksi antrean (panggil, skip, recall) | Semua tombol render dan emit event yang benar | `Todo` |
|
|
| CT-005 | `TabelPatientData.vue` | Render tabel daftar pasien | Kolom: nama, noRM, antrean, status, aksi tampil benar | `Todo` |
|
|
| CT-006 | `SideBar.vue` | Menu render sesuai hak akses user | Menu yang tidak diizinkan tidak tampil | `Todo` |
|
|
| CT-007 | `PageHeader.vue` | Render judul halaman dan breadcrumb | Judul dan navigasi sesuai route aktif | `Todo` |
|
|
| CT-008 | `AppSnackbar.vue` | Notifikasi muncul dan auto-dismiss | Snackbar tampil 3 detik lalu hilang | `Todo` |
|
|
| CT-009 | `SelectionDialog.vue` | Dialog pilihan dengan konfirmasi | Pilihan terseleksi, emit event saat konfirmasi | `Todo` |
|
|
| CT-010 | `ProfileMenu.vue` | Tampil info user dan tombol logout | Nama user tampil, klik logout memanggil `useAuth().logout()` | `Todo` |
|
|
|
|
### 3.5 E2E Tests (Cypress)
|
|
|
|
| ID | Flow | Steps | Expected | Status |
|
|
|----|------|-------|----------|--------|
|
|
| E2E-001 | Login | 1. Buka `/` 2. Redirect ke `/LoginPage` 3. Klik login Keycloak 4. Isi credentials | Redirect ke `/dashboard`, user session aktif | `Skeleton` |
|
|
| E2E-002 | Anjungan — Ambil Antrean | 1. Buka `/anjungan` 2. Pilih klinik 3. Konfirmasi | Nomor antrean di-generate, tiket tampil | `Todo` |
|
|
| E2E-003 | Anjungan Eksekutif — Pilih Subspesialis | 1. Buka `/anjungan` 2. Pilih klinik eksekutif 3. Pilih subspesialis 4. Konfirmasi | Antrean tercipta dengan subspesialis terpilih | `Todo` |
|
|
| E2E-004 | Check-in QR | 1. Buka `/check-in-pasien/check-in` 2. Scan QR valid | Status pasien update ke "hadir", notifikasi sukses | `Todo` |
|
|
| E2E-005 | Loket — Panggil Pasien | 1. Login sebagai admin loket 2. Buka `/admin-loket` 3. Klik "Panggil Berikutnya" | Pasien berikutnya tampil di current patient card | `Todo` |
|
|
| E2E-006 | Loket — Skip & Recall | 1. Panggil pasien 2. Klik skip 3. Klik recall | Pasien di-skip lalu bisa di-recall kembali | `Todo` |
|
|
| E2E-007 | Klinik — Lihat Daftar Pasien | 1. Login sebagai admin klinik 2. Buka `/admin-klinik` | Daftar pasien klinik tampil sesuai klinik user | `Todo` |
|
|
| E2E-008 | Dashboard — Statistik | 1. Login sebagai superadmin 2. Buka `/dashboard` | Chart statistik dan data antrean tampil | `Todo` |
|
|
| E2E-009 | Setting — CRUD Master Klinik | 1. Buka `/setting/master-klinik` 2. Tambah klinik 3. Edit 4. Hapus | Data klinik berhasil CRUD tanpa error | `Todo` |
|
|
| E2E-010 | Setting — Hak Akses | 1. Buka `/setting/hak-akses` 2. Pilih role & group 3. Set permission 4. Simpan | Hak akses tersimpan dan efektif | `Todo` |
|
|
| E2E-011 | WebSocket Sync | 1. Buka admin loket di tab A 2. Buka display screen di tab B 3. Panggil pasien di tab A | Tab B menampilkan nomor panggil dalam < 2 detik | `Todo` |
|
|
| E2E-012 | Auth Guard | 1. Tanpa login, akses `/dashboard` | Redirect ke `/LoginPage` | `Todo` |
|
|
|
|
---
|
|
|
|
## 4. Definition of Done (DoD)
|
|
|
|
Sebuah task/story dianggap **Done** jika:
|
|
|
|
- [ ] Code sudah diimplementasi dan berjalan di dev server
|
|
- [ ] Unit test ditulis untuk logic kritis (composable, store)
|
|
- [ ] TypeScript: tidak ada error pada `npx nuxi typecheck`
|
|
- [ ] ESLint: tidak ada error (`npx eslint .`)
|
|
- [ ] Code review dilakukan minimal 1 orang
|
|
- [ ] Tested manual di browser Chrome (target utama)
|
|
- [ ] WebSocket sync diverifikasi antar device (jika fitur terkait real-time)
|
|
- [ ] Responsive layout dicek untuk tablet (anjungan) dan desktop (admin)
|
|
- [ ] Tidak ada `console.log` debugging yang tertinggal di production path
|
|
- [ ] State antrean terisolasi per loket (jika fitur terkait loket)
|
|
- [ ] Polling fallback 30 detik berfungsi sebagai safety net
|
|
|
|
---
|
|
|
|
## 5. Code Review Checklist
|
|
|
|
### General
|
|
- [ ] Logic mudah dibaca dan dipahami
|
|
- [ ] Tidak ada dead code atau file `.txt` backup yang tersisa
|
|
- [ ] Error handling ada di semua `$fetch` / `useFetch` call
|
|
- [ ] Tidak ada hardcoded IP/URL (gunakan `.env` + `runtimeConfig`)
|
|
- [ ] Tidak ada `console.log` di high-frequency path (WebSocket handler, polling loop)
|
|
|
|
### Vue 3 + TypeScript
|
|
- [ ] `<script setup>` digunakan (Composition API)
|
|
- [ ] Props & emits memiliki type yang jelas
|
|
- [ ] Minimal penggunaan `any` type (justified jika ada)
|
|
- [ ] Composables dipakai untuk logic reusable (`composables/use*.ts`)
|
|
- [ ] Reactive data menggunakan `ref()` / `reactive()` dengan benar
|
|
- [ ] `watch` / `computed` digunakan daripada manual mutation
|
|
|
|
### Nuxt 3 Specific
|
|
- [ ] Data fetching pakai `$fetch` / `useFetch` / `useAsyncData`
|
|
- [ ] Middleware auth terpasang di route yang memerlukan login
|
|
- [ ] Server routes (proxy) mengikuti konvensi `server/routes/[prefix]/[...].ts`
|
|
- [ ] Internal API mengikuti konvensi `server/api/[resource].[method].ts`
|
|
- [ ] Environment variables diakses via `useRuntimeConfig()`
|
|
|
|
### Pinia Store
|
|
- [ ] Store menggunakan `defineStore()` dengan naming `use[Name]Store`
|
|
- [ ] State yang perlu persisten menggunakan `pinia-plugin-persistedstate`
|
|
- [ ] Key persisted state unik per loket/klinik (hindari konflik antar instance)
|
|
- [ ] `allPatients` tetap single source of truth (tidak duplikasi state)
|
|
|
|
### WebSocket
|
|
- [ ] Client ID deterministic (bukan random) untuk targetable messaging
|
|
- [ ] Auto-reconnect logic aktif
|
|
- [ ] Polling fallback 30 detik sebagai safety net
|
|
- [ ] Event handler tidak melakukan full re-render (surgical update)
|
|
|
|
---
|
|
|
|
## 6. Standar Kode
|
|
|
|
### Naming Convention
|
|
|
|
| Tipe | Konvensi | Contoh (Aktual) |
|
|
|------|----------|-----------------|
|
|
| Page (Vue) | PascalCase | `Dashboard.vue`, `AdminLoket.vue` |
|
|
| Component | PascalCase | `PatientCard.vue`, `QueueActionsCard.vue` |
|
|
| Composable | camelCase + `use` prefix | `useWebSocket.ts`, `useQueue.js` |
|
|
| Store (Pinia) | camelCase + `Store` suffix | `queueStore.js`, `clinicStore.js` |
|
|
| Middleware | camelCase | `auth.ts`, `permissions.ts` |
|
|
| Type/Interface | PascalCase | `User`, `Permission`, `ApiResponse<T>` |
|
|
| Constants | SCREAMING_SNAKE | `API_BASE_URL`, `WS_API_URL` |
|
|
| CSS Class | kebab-case | `.patient-card`, `.queue-actions` |
|
|
| Server API | `[resource].[method].ts` | `permission.get.ts`, `validate-token.post.ts` |
|
|
| Server Proxy Route | `[...].ts` di folder prefix | `server/routes/visit-api/[...].ts` |
|
|
|
|
### File Organization Rules
|
|
|
|
```
|
|
# ✅ Good — composable terpisah untuk concern berbeda
|
|
composables/
|
|
useAuth.ts ← Authentication logic
|
|
useWebSocket.ts ← WebSocket connection management
|
|
useQueue.js ← Queue business logic
|
|
useCheckIn.ts ← Check-in flow
|
|
useThermalPrint.ts ← Thermal printer integration
|
|
|
|
# ✅ Good — komponen terorganisir per fitur
|
|
components/
|
|
common/ ← Reusable (AppSnackbar, Avatar, PageHeader)
|
|
layout/ ← Layout (SideBar, ProfileMenu)
|
|
features/
|
|
queue/ ← PatientCard, CurrentPatientCard, QueueActionsCard
|
|
antrean/ ← Komponen anjungan
|
|
master/ ← Komponen setting master data
|
|
monitoring/ ← Komponen monitoring pasien
|
|
|
|
# ❌ Bad — logic besar langsung di <script setup> page
|
|
pages/AdminLoket.vue ← Jangan taruh >100 baris logic di sini, extract ke composable
|
|
```
|
|
|
|
### Code Pattern — Composable
|
|
|
|
```typescript
|
|
// ✅ Good — typed composable dengan error handling
|
|
export function useClinicAPI() {
|
|
const config = useRuntimeConfig()
|
|
|
|
async function fetchClinics(): Promise<Clinic[]> {
|
|
try {
|
|
const data = await $fetch('/klinik-api/clinics')
|
|
return data
|
|
} catch (error) {
|
|
console.error('Failed to fetch clinics:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
return { fetchClinics }
|
|
}
|
|
|
|
// ❌ Bad — untyped, no error handling
|
|
export function useClinicAPI() {
|
|
async function fetchClinics() {
|
|
const data = await $fetch('/klinik-api/clinics') // bisa crash
|
|
return data
|
|
}
|
|
return { fetchClinics }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. CI/CD Quality Gates
|
|
|
|
> **Catatan:** CI/CD belum diimplementasi. Berikut rencana pipeline saat siap.
|
|
|
|
```yaml
|
|
# GitHub Actions — quality checks (planned)
|
|
name: Quality Gate
|
|
|
|
on:
|
|
push:
|
|
branches: [main, dev]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
quality:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Install Dependencies
|
|
run: npm ci
|
|
|
|
- name: Type Check
|
|
run: npx nuxi typecheck
|
|
|
|
- name: Lint
|
|
run: npx eslint .
|
|
|
|
- name: Unit Tests
|
|
run: npx vitest run --coverage
|
|
|
|
- name: Build
|
|
run: npx nuxt build
|
|
```
|
|
|
|
### Manual Quality Gate (Saat Ini)
|
|
|
|
Sebelum merge / deploy, lakukan checklist manual berikut:
|
|
|
|
- [ ] `npx nuxi typecheck` — tidak ada error
|
|
- [ ] `npx eslint .` — tidak ada error
|
|
- [ ] `npm run test` — semua test pass
|
|
- [ ] `npm run build` — build sukses tanpa error
|
|
- [ ] Test manual di browser Chrome di jaringan LAN (`http://10.10.150.175:3000`)
|
|
- [ ] Verifikasi WebSocket sync antara admin & display screen
|
|
- [ ] Pastikan Docker build berhasil (`docker compose build`)
|
|
|
|
---
|
|
|
|
## 8. Bug Severity Matrix
|
|
|
|
| Level | Deskripsi | Contoh di Web Antrean | SLA Fix |
|
|
|-------|-----------|----------------------|---------|
|
|
| 🔴 Critical | App crash / data loss / security breach | WebSocket down total → antrean tidak sync, Keycloak auth bypass, pasien kehilangan antrean | < 4 jam |
|
|
| 🟠 High | Fitur utama tidak bisa dipakai | Tombol panggil pasien tidak berfungsi, anjungan tidak bisa generate antrean, QR scanner error | < 1 hari |
|
|
| 🟡 Medium | Fitur minor terganggu, ada workaround | Thermal print gagal (pasien masih bisa lihat di layar), statistik dashboard delayed | < 3 hari |
|
|
| 🟢 Low | UI/kosmetik, tidak mengganggu fungsi | Alignment card tidak rapi, warna badge sedikit off, tooltip tidak muncul | Backlog |
|
|
|
|
### Known Issues & Mitigasi
|
|
|
|
| Issue | Severity | Mitigasi Saat Ini |
|
|
|-------|----------|-------------------|
|
|
| WebSocket disconnect saat jaringan LAN tidak stabil | 🔴 | Auto-reconnect + polling fallback 30 detik |
|
|
| Endpoint 500 menyebabkan request spam | 🟠 | Blacklist endpoint gagal di `doctorStore.js` |
|
|
| `console.log` verbose di WebSocket handler | 🟢 | Dibersihkan di high-frequency path |
|
|
| Race condition antar loket | 🟠 | State isolasi per `loketId` + unique storage key |
|
|
| Mixed JS/TS — beberapa store masih `.js` | 🟢 | Migrasi bertahap ke `.ts` |
|
|
|
|
---
|
|
|
|
## 9. Technical Debt Tracker
|
|
|
|
| Item | Impact | Priority | Plan |
|
|
|------|--------|----------|------|
|
|
| Store files masih `.js` (queueStore, clinicStore, dll) | Type safety rendah | 🟡 Medium | Migrasi bertahap ke TypeScript |
|
|
| `queueStore.js` terlalu besar (141KB) | Sulit maintain & test | 🟠 High | Pecah menjadi sub-stores per concern |
|
|
| Tidak ada test coverage saat ini | Regresi tidak terdeteksi | 🟠 High | Mulai dari composable kritis |
|
|
| CI/CD belum ada | Manual quality gate | 🟡 Medium | Setup GitHub Actions |
|
|
| Beberapa file backup (`.txt`, `old_*.vue`) masih ada | Noise di codebase | 🟢 Low | Cleanup & gitignore |
|
|
|
|
---
|
|
|
|
## 10. Changelog
|
|
|
|
| Versi | Tanggal | Author | Perubahan |
|
|
|-------|---------|--------|-----------|
|
|
| 1.0.0 | 2026-05-25 | Akbar | Initial QMD — dibuat berdasarkan kondisi project aktual |
|