# Design

## Context

Lihat proposal.md untuk motivasi. Reports saat ini membaca getReport() dari data.ts, memakai empat tab, ExcelJS di browser, dan print window untuk PDF. Dashboard memakai snapshot read-only dan membedakan cohort bulanan dengan kondisi terkini. WorkOrder memiliki timestamps dan status history; FailureEvent, OperatingLog, serta StockoutEvent belum memiliki alur pencatatan yang memenuhi kebutuhan laporan. Service reports.service.ts existing menangani DamageReport.

## Goals / Non-Goals

**Goals:** agregasi seluruh hasil filter, definisi periode konsisten, rincian yang dapat ditelusuri, ekspor lengkap dan terbatas ukurannya, penggunaan pola existing tanpa mengubah workflow domain.

**Non-Goals:** database snapshot permanen, audit perubahan dueAt, jam kerja aktif, saldo stok historis, penilaian finansial, pencatatan kegagalan/stockout, atau perubahan Dashboard. Tidak menambahkan library UI/export baru.

## Decisions

### 1. Struktur dan endpoint

Tambahkan routes/reports.routes.ts, routes/reports.openapi.ts, controllers/report-analytics.controller.ts, validators/report-analytics.schema.ts, services/report-analytics.service.ts dan helper metrik bila diperlukan. DTO publik packages/shared/reports.ts diekspor dari index.ts. Registrasikan router di /api/v1/reports. Service DamageReport existing tidak diganti atau dinamai ulang.

GET /work-orders, /corrective, /inventory, /compliance mengembalikan summary, chart datasets, dan detail terpagina dalam envelope existing. GET /export-data mengembalikan dataset JSON semua tab tanpa pagination untuk pembentukan Excel/PDF di browser. Alternatif mengambil seluruh halaman satu per satu ditolak karena rawan campuran snapshot dan truncation.

Semua endpoint requireAuth untuk role aplikasi aktif seperti Dashboard, private/no-store, tidak membuka akses anonim. Setiap respons membaca snapshot konsisten melalui transaction client bersama; export membaca semua tab dalam satu snapshot. Jangan memanggil scheduler atau mutation service. SQL agregasi bila perlu menggunakan parameter, bukan interpolasi filter mentah.

### 2. Filter dan batas ukuran (default usulan untuk review)

dateFrom/dateTo wajib pada API; UI default bulan berjalan sampai hari ini. Preset bulan ini, bulan lalu, kuartal ini, dan kustom. Batas 366 hari per request; tanggal akhir dapat mencakup sisa bulan/kuartal berjalan, tetapi hasil berhenti pada generatedAt. Rentang seluruhnya di masa depan ditolak. Batas ekspor gabungan 10000 baris detail; gagal eksplisit dengan pesan mempersempit periode/filter, bukan memotong data.

Source filter berlaku untuk WO/corrective; type hanya WO; groupId/partId untuk inventory. Compliance khusus PREVENTIVE, tidak mewarisi filter CORRECTIVE atau sumber karantina. Export menangkap filter setiap tab secara eksplisit sebagai query tervalidasi dengan nama yang tidak ambigu, misalnya woSource, woType, correctiveSource, inventoryGroupId, inventoryPartId. Semua tab menggunakan tanggal global yang sama.

Pagination default page=1/pageSize=20 maksimum100. WO detail menyediakan cohort=created|completed|backlog (default completed); KPI/chart tetap merangkum ketiga cohort dengan label jelas. Urutan WO/compliance tanggal relevan lalu ID; ranking count descending lalu target ID; inventory SKU lalu part ID.

### 3. Waktu dan status pada cutoff

Gunakan timezone SystemSetting dan helper tanggal existing. Filter inklusif diterjemahkan menjadi [start,endExclusive). Untuk periode selesai, peristiwa tepat endExclusive tidak masuk; untuk periode berjalan, batasi sampai generatedAt dengan aturan equality yang terdokumentasi. dueAt sama dengan cutoff sekarang sudah eligible; lateness positif hanya jika melampaui dueAt.

Gunakan timestamps terminal completedAt/cancelledAt dan history untuk status pada cutoff, bukan hanya status sekarang. History menjadi fallback untuk timestamps terminal legacy yang kosong. Jika completed tanpa waktu yang dapat dipulihkan, tandai kualitas data; jangan mengarang completion date atau durasi. Due dates, nama, kelompok, dan satuan memakai nilai terkini dan diberi disclaimer. Tidak memerlukan snapshot schema baru.

### 4. Cohort maintenance

WO dibuat mengikuti createdAt; selesai mengikuti completedAt; backlog mencakup seluruh WO yang sudah dibuat/dirilis sebelum cutoff tanpa terminal state efektif pada cutoff, termasuk WO sebelum awal periode. Batalkan-effective-at-cutoff dikecualikan dari metrik operasional. Rata-rata durasi seluruh WO: hanya completion cohort dengan pair valid, tampilkan sampleCount dan excludedCount; tidak disebut downtime.

Corrective menggunakan CM creation cohort non-cancelled-at-cutoff. Ranking seluruh target dalam cohort; completion subset dan rata-rata durasi menggunakan anggota cohort yang selesai sampai cutoff. Pilot Plant group key kind+ID; quarantine group key partId, bukan nama atau quarantineId. Pisahkan hasil sumber untuk menghindari interpretasi satu SKU sebagai satu unit mesin.

PM denominator adalah WO PM dirilis dengan dueAt pada periode dan sudah tercapai pada cutoff; tanpa dueAt dan cancelled-effective-at-cutoff dikecualikan. Numerator completion<=dueAt. Pembagian onTime/late/unfinished/unknown menjumlah denominator. Plan occurrences yang belum menjadi WO tidak dihitung. Compliance per equipment mengikuti hierarchy Item -> Equipment; sertakan nama category sebagai konteks. Detail keterlambatan dihitung dalam menit, UI menampilkan hari/jam agar keterlambatan kurang sehari tidak dibulatkan menjadi nol hari.

### 5. Ledger inventory

Gunakan postedAt dan physicalDelta/quarantineDelta existing. Original non-reversal RECEIPT/ISSUE/RETURN menghasilkan gross quantity dan frequency masing-masing; distinct issued SKUs/top10 frequency hanya original ISSUE. Reversal diidentifikasi lebih dahulu melalui reversalOfId sehingga tidak dihitung ulang sebagai ISSUE/RECEIPT walaupun type diwariskan.

Rekap per SKU menyimpan receiptQty, issueQty, returnQty, correctionPhysicalDelta, unit. Koreksi mencakup reversal dan adjustment fisik lainnya; rincian dipisahkan menurut jenis termasuk SCRAP. Net physical movement = receiptQty - issueQty + returnQty + correctionPhysicalDelta. QUARANTINE/RELEASE dengan physicalDelta nol tidak menjadi pengeluaran fisik. Tidak menambahkan perhitungan saldo awal/akhir pada tahap ini.

Pendekatan gross+correction menjaga audit lintas periode; alternatif menghapus transaksi original setelah reversal ditolak karena mengubah angka periode lama tanpa jejak. Return tetap kategori sendiri, tidak disembunyikan sebagai reversal. Arsip part tidak menghilangkan ledger historis; quantities berbeda unit tidak dijumlahkan.

### 6. Frontend dan ekspor

Tambahkan services/reports.service.ts dan hooks/reports.queries.ts dengan shared query client dan DTO. Query key mencakup tab/filter/cohort/page; batalkan request usang dengan signal, reset halaman pada perubahan filter, tampilkan Skeleton awal serta stale/error/empty yang berbeda. Hindari menampilkan hasil filter lama di bawah label filter baru. Pertahankan komponen dan gaya empat tab.

Ubah data.ts menjadi konfigurasi label/types bila masih digunakan; hapus fungsi dummy hanya setelah pemeriksaan pemakai. Ganti tabel teknisi, biaya, kategori kerusakan, dan metrik unsupported sesuai proposal. Gunakan date-filter existing sebagai referensi tanpa mengubah file inventory yang sedang diedit pengguna.

WO/compliance menyediakan navigasi ke detail Maintenance yang sudah ada; inventory ke Stock Movement dengan SKU bila routing mendukung. Jika route belum menerima target, tambahkan penerimaan parameter minimal untuk membuka detail/filter existing, tanpa redesign atau mutation baru. Corrective ranking dapat menampilkan daftar WO target sebelum navigasi detail.

Export memakai frozen filter object dan satu /export-data, lalu formatter murni yang sama untuk layar/export. Excel empat sheet dengan grafik PNG beresolusi 2x + sheet Data Grafik untuk angka sumber; grafik merupakan gambar, bukan chart Excel yang dapat diedit. PDF memakai react-to-pdf (method build) untuk dokumen awal, dilanjutkan tangkapan html2canvas per halaman ke instance PDF yang dikembalikan, lalu langsung diunduh. Isi PDF berupa gambar, bukan teks/grafik vektor. Tabel diukur dan dipecah per halaman, sehingga tidak membangun satu canvas untuk 10.000 baris. Keduanya menangkap komponen Recharts existing yang dirender dari snapshot export. Dokumen iframe sementara berukuran tetap mengisolasi CSS hex/rgb dari Tailwind oklch/oklab agar kompatibel dengan html2canvas, serta dibersihkan dalam finally. Excel memakai canvas.toDataURL sebagai base64 untuk ExcelJS addImage. PDF tidak memakai popup/print lagi. Grafik panjang dibagi menjadi bagian dengan skala yang sama agar label dan seluruh equipment tetap terbaca. Dataset kosong menampilkan keterangan tanpa grafik kosong. Hindari Object.keys(rows[0]) pada dataset kosong, tulis header dari schema eksplisit. Gunakan string cell untuk teks user, bukan formula. Tangani kegagalan capture/request, double-click, iframe/canvas dan URL cleanup. Tanggal/label ekspor berasal dari response metadata, bukan state UI yang mungkin berubah.

## Risks / Trade-offs

- DueAt/metadata dapat berubah -> ungkapkan keterbatasan historical recalculation di halaman dan ekspor; snapshot permanen di luar scope.
- Historical backlog memerlukan status rekonstruksi -> uji cancellation/completion lintas periode dan history legacy; jangan memakai status terkini saja.
- Ranking WO tidak sama dengan kegagalan unit -> label WO corrective dan sumber target eksplisit.
- Banyak transaksi -> agregasi database, pagination, bounded range/export, count sebelum materialisasi detail; tinjau query plan bila dataset uji menunjukkan bottleneck, tanpa menambah index/migration diam-diam.
- API tab terpisah di-refresh pada waktu berbeda -> metadata generatedAt masing-masing; export snapshot tunggal sebagai laporan konsisten.
- Model masih memiliki field finansial legacy -> DTO memilih field eksplisit; tidak menghapus kolom di perubahan ini.

## Migration Plan

Tidak ada migration atau reseed. Implementasikan DTO/API dan tes dahulu, kemudian consumer dan ekspor. Verifikasi regresi Dashboard, DamageReport, dan Stock Movement. Rollback melalui pengembalian perubahan aplikasi; tidak ada data bisnis yang ditulis Reports. Jangan menjalankan build:vercel sebagai validasi karena menulis database.
