# Design

## Context

Lihat proposal.md untuk tujuan perubahan. `pages/home/page.tsx` memiliki empat KPI hardcoded, lima komponen grafik, dan tabel yang mengimpor `home/data.ts`. Dataset lama mencakup Calibration, teknisi, serta status Overdue sebagai pengganti status WO. Tombol View all belum terhubung.

API sudah menggunakan routes -> controllers -> services -> Prisma, envelope bersama, sesi aktif, dan OpenAPI per fitur. `inventory-catalog.service.ts` menghitung available dari physical minus quarantined pada semua balance SKU. Model berada di `prisma/models`; `prisma/schema.prisma` adalah konfigurasi schema terpisah, bukan satu-satunya sumber model.

WorkOrder sudah memiliki source, releaseAt, dueAt, completedAt, status, priority, dan relasi target. Status overdue merupakan turunan waktu. Maintenance calendar juga mempunyai proyeksi yang belum menjadi WO, sementara generator occurrence berjalan melalui perintah terpisah. Dashboard tidak boleh memanggil generator saat GET. Pola pengujian tersedia di `test/maintenance-recurrence.test.ts`, kontrak fitur, serta pengujian MySQL terisolasi.

## Goals / Non-Goals

**Goals:** Satu kontrak agregasi terukur, batas waktu yang konsisten, DTO ringan, dan frontend yang mengomunikasikan cakupan angka secara jujur. Menggunakan model yang tersedia tanpa migrasi.

**Non-Goals:** Historical snapshot/time travel, date picker, tren persentase dibanding periode sebelumnya, target compliance baru, scheduler, notifikasi, mutasi WO/stok, ekspor, assignment teknisi, atau penghapusan calibration dari seluruh aplikasi/database.

## Decisions

### 1. Satu endpoint agregasi khusus

Tambahkan `dashboard.routes.ts`, `dashboard.controller.ts`, `dashboard.service.ts`, `dashboard.schema.ts`, dan `dashboard.openapi.ts` di folder masing-masing. Daftarkan endpoint `GET /api/v1/dashboard/overview` pada router yang ada. Semua role aplikasi dengan sesi aktif dapat membaca ringkasan global; tidak ada akses kiosk anonim. Gunakan envelope/error middleware proyek dan DTO eksplisit, bukan bentuk model Prisma.

Alternatif memanggil banyak list endpoint dari browser ditolak karena pagination membuat agregasi salah dan list WO saat ini memuat detail yang tidak dibutuhkan. Tidak perlu repository baru atau endpoint CRUD Dashboard.

MVP tidak menerima parameter periode: bulan berjalan ditentukan server. Validator/kontrak memastikan request tidak diam-diam mengklaim mendukung dateFrom/dateTo; tolak filter yang tidak didukung mengikuti respons validasi proyek. Struktur data yang direncanakan:

```text
data
  asOf, timezone
  period { start, endExclusive, label }
  kpis
    pmCompliance { onTime, eligible, percentage }
    overduePm, activeCorrective, lowStockSkus
  maintenance
    woByType [{ type, total, completed }]
    pmStatus { completed, overdue, open, inProgress, total }
  inventory
    availableByGroupAndUnit [{ groupId, groupName, unit, available }]
    critical { emptySkus, lowSkus, zeroMinimumSkus, quarantinedSkus }
    conditionByGroup [{ groupId, groupName, safe, low, empty, quarantined, uniqueSkus }]
  workOrders [{ id, number, type, source, target, priority, status, dueAt, isOverdue }]
```

`target` memuat label, kode, dan konteks induk opsional: equipment/item untuk pilot plant, nama part/SKU untuk karantina. Tidak memuat identitas teknisi, email, biaya, atau data pribadi lainnya.

### 2. Definisi waktu dan cohort

Tangkap satu `asOf` per permintaan. Gunakan zona waktu fasilitas dari konfigurasi existing (default Asia/Jakarta), awal bulan inklusif dan awal bulan berikutnya eksklusif. Gunakan helper waktu existing; jangan menghitung batas bulan berdasarkan timezone browser. Semua cohort WO mengecualikan releaseAt > asOf, CALIBRATION, dan CANCELLED.

| Bagian | Dasar perhitungan |
|---|---|
| PM compliance | PREVENTIVE dueAt dalam bulan berjalan dan dueAt <= asOf. Pembilang COMPLETED dengan completedAt <= dueAt; penyebut seluruh WO eligible tersebut. Bulatkan satu desimal, null jika penyebut nol. |
| PM overdue | Seluruh PREVENTIVE OPEN/IN_PROGRESS dengan dueAt < asOf, lintas bulan. |
| Corrective aktif | Seluruh CORRECTIVE OPEN/IN_PROGRESS, lintas bulan dan kedua sumber target. |
| WO menurut jenis | WO dibuat dalam bulan berjalan; total dan subset COMPLETED. |
| Status PM | PM dueAt dalam bulan berjalan, termasuk tenggat yang masih akan datang bila WO sudah released. Completed mencakup tepat waktu maupun terlambat. Sisanya overdue aktif, open, atau in-progress; segmen saling eksklusif. |

PM tanpa dueAt tidak masuk compliance/status PM maupun overdue, tetapi tetap dapat muncul di grafik WO-created dan tabel. Completed tanpa completedAt tidak memenuhi pembilang compliance. Tenggat persis asOf sudah masuk penyebut tetapi belum overdue; completedAt persis dueAt tepat waktu.

Alternatif membagi semua PM selesai dengan semua PM dijadwalkan ditolak sesuai pilihan pengguna A. PM yang selesai lebih awal tetapi belum jatuh tempo tidak masuk pembilang maupun penyebut sampai deadline tercapai; ini menjaga pembandingan terhadap kewajiban yang sudah jatuh tempo. Grafik status PM dan compliance sengaja memiliki penyebut berbeda dan dijelaskan pada keterangan UI.

### 3. Stok mengikuti sumber data Inventory

Agregasikan StockBalance per Part aktif terlebih dahulu, termasuk balance yang sudah tersedia dalam model, tanpa filter lokasi baru. Part tanpa balance diperlakukan nol. Gunakan current balance, bukan penjumlahan quantity awal kasus karantina atau jumlah notifikasi StockoutEvent.

- `available = sum(physical) - sum(quarantined)`.
- Empty: available = 0; low: 0 < available < minimumStock; safe: available > 0 dan available >= minimumStock.
- KPI critical = jumlah SKU empty atau low, masing-masing dihitung satu kali.
- Rusak/karantina = SKU dengan quarantined > 0; dapat beririsan dengan kondisi available.
- Minimum nol = minimumStock = 0. Model tidak membedakan belum diisi dengan sengaja nol; gunakan label "Minimum stok = 0", bukan menyatakan data pasti belum diisi.
- Kuantitas stok tersedia dikelompokkan berdasarkan groupId dan unit tersimpan. Tampilkan baris/bar berlabel kelompok + satuan; jangan jumlahkan Pcs dengan Meter atau mengonversi satuan tanpa aturan.

Alternatif mengubah grafik kuantitas menjadi jumlah SKU tidak dipilih agar makna grafik existing tetap dipertahankan. Grafik kondisi tetap menghitung SKU dan mempertahankan penjelasan existing bahwa kategori karantina dapat beririsan. Pending checkout belum mengubah balance, sehingga tidak mengurangi available lagi. Pembatalan transaksi dan pelepasan karantina otomatis tercermin dari balance hasil posting.

### 4. Daftar perhatian terbatas

Ambil maksimal lima WO eligible, kedua sumber target, tanpa menggandakan record. Urutan: (1) aktif overdue, (2) aktif URGENT yang belum masuk kategori pertama, (3) sisanya terbaru. Kategori pertama diurutkan dueAt naik, priority URGENT/HIGH/MEDIUM/LOW, createdAt turun, ID turun. Kategori kedua dan ketiga menggunakan createdAt turun lalu ID turun.

Cancelled dan calibration dikecualikan; completed dapat mengisi bagian terbaru. Pertahankan status aktual dan `isOverdue` terpisah. Target yang diarsipkan tidak menghapus riwayat WO dari ringkasan. Bila relasi target legacy tidak lengkap, gunakan fallback eksplisit "Target tidak tersedia" tanpa membuat nama dummy. Alternatif hanya latest lima ditolak karena dapat menyembunyikan backlog mendesak yang lama.

### 5. Konsistensi dan biaya query

Gunakan transaksi baca snapshot konsisten pada database existing dan satu asOf untuk seluruh agregasi. Pilih field minimal, agregasi database sejauh sesuai, dan query target hanya untuk lima row terpilih; hindari memanggil detail service untuk setiap WO dalam populasi. Agregasi menghitung semua record eligible sebelum pembatasan tabel. Parameterisasi query jika perbandingan completedAt/dueAt atau available/minimum memerlukan SQL.

Tidak ada cache global pada MVP; menghindari kebutuhan invalidasi lintas proses. Batasi pekerjaan transaksi pada database, tanpa network call, generator, atau publisher notifikasi. Jika profiling menunjukkan kebutuhan index/model tambahan, konfirmasi terlebih dahulu sebelum mengubah schema. Jangan menutupi kegagalan query dengan angka nol.

### 6. Integrasi dan perubahan UI untuk direview

Tambahkan service `apps/app/src/services/dashboard.service.ts` mengikuti auth.service/service-http dan hook `apps/app/src/hooks/dashboard.queries.ts` mengikuti notifications. Query aktif setelah sesi tersedia, terisolasi antar sesi, dibersihkan bersama cache sesi ketika logout, dan tidak menampilkan cache pengguna sebelumnya. Refetch saat kembali fokus serta tiap 60 detik ketika halaman aktif; jangan polling background tab. Gunakan pembatalan request mengikuti pola proyek.

Satu query memasok seluruh komponen lewat props. Hapus data operasional dummy `home/data.ts` bila tidak lagi digunakan; pindahkan metadata warna/label statis yang masih perlu ke tempat yang sesuai. Tidak perlu library atau state manager baru.

| Perubahan tampilan | Alasan / status review |
|---|---|
| Hapus Calibration dan Technician/avatar | Sudah disetujui pengguna; hanya dashboard. |
| PM compliance menulis "selesai tepat waktu dari PM yang sudah jatuh tempo" | Sudah disepakati makna A; hindari klaim "sesuai target" tanpa konfigurasi target. |
| Label corrective menjadi "Corrective Aktif" | Usulan agar OPEN dan IN_PROGRESS tidak disalahartikan sebagai antrean saja. |
| Keterangan bulan berjalan dan kondisi saat ini | Usulan untuk membedakan cohort bulanan dari backlog/stok lintas bulan. |
| PM donut menampilkan Completed, Overdue, Open, In Progress | Usulan agar PM belum jatuh tempo dan yang sedang dikerjakan tidak hilang. Footer menyebut completion, terpisah dari KPI on-time. |
| Tabel Equipment/Sub Item menjadi Target/Sumber | Usulan agar sparepart karantina dapat tampil tanpa dipaksakan menjadi equipment; konteks item/induk tetap ditampilkan di target. |
| Badge URGENT dan indikator overdue terpisah | Usulan menyesuaikan enum serta status aktual API. |
| Grafik kuantitas berlabel kelompok + satuan; label minimum nol | Usulan mencegah pencampuran satuan dan asumsi minimum belum diisi. |
| Loading, error/retry, empty, peringatan refresh gagal | Usulan kebutuhan integrasi data asli; tidak menggunakan fallback dummy. |
| View all menuju Maintenance | Mengaktifkan tombol yang sudah tersedia; tidak menambah halaman baru. |

Susunan empat KPI, lima grafik, dan satu tabel tetap. Subtitle cakupan mencakup pilot plant dan sparepart. Tampilkan catatan singkat bahwa metrik PM berdasarkan WO yang sudah dibuat. Nilai null memakai em dash; angka nol tetap angka nol. Warna dan keterangan KPI mengikuti kondisi data, tanpa tren/target fiktif. Matriks ini menjadi bagian review proposal sebelum apply, bukan izin untuk perubahan UI lain di luar daftar.

## Risks / Trade-offs

- [Generator WO belum dijalankan] -> Angka PM hanya mencakup WO aktual; jelaskan cakupan, jangan membuat occurrence pada GET. Kelengkapan jadwal bergantung proses existing.
- [PM bulanan dan total WO berbeda] -> Label dasar cohort dueAt vs createdAt secara eksplisit; grafik status completion bukan compliance tepat waktu.
- [Data berubah setelah dibaca] -> Tampilkan asOf dan refetch berkala; satu snapshot menjamin konsistensi dalam satu respons, bukan realtime push.
- [Perubahan dueAt/cancellation mengubah metrik] -> Gunakan keadaan terkini; bukan laporan historis immutable. Tidak menambah tabel snapshot dalam MVP.
- [Satuan bebas menghasilkan label mirip] -> Pertahankan unit tersimpan tanpa konversi diam-diam; normalisasi master data di luar scope.
- [Beban agregasi bertambah] -> Proyeksi field minimal, query bounded untuk daftar, dan pengujian query terhadap dataset representatif; usulkan index hanya bila terbukti diperlukan.
- [Target legacy tidak lengkap] -> Fallback label eksplisit; jangan menghilangkan WO dan menurunkan backlog tanpa penjelasan.

## Migration Plan

1. Implementasikan dan verifikasi kontrak backend beserta OpenAPI; tidak ada migrasi Prisma/ERD.
2. Integrasikan service/hook dan komponen frontend setelah review perubahan UI disetujui lewat apply.
3. Uji skenario angka, batas waktu, auth, no-side-effects, serta render normal/kosong/gagal dan navigasi; jalankan typecheck/build yang relevan.
4. Deploy backend sebelum frontend agar endpoint tersedia. Rollback frontend/backend secara berurutan bila perlu; tidak ada rollback data atau backfill. Jangan menjalankan seeder produksi untuk mengisi dashboard.
