| @@ -236,80 +236,3 @@ Isi email: subject `Due today: <ref> needs your <role> response` atau `Past due: | |||
| 236 | 236 | Balasan 400 memakai format validasi Zod yang sudah dipakai endpoint project lain. Tidak ada kode error khusus. | |
| 237 | 237 | ||
| 238 | 238 | Hindari menulis nilai di luar rentang lewat jalur lain (script, migrasi). Nilai rusak bisa membuat pembacaan settings gagal di service lain. | |
| 239 | - | ||
| 240 | - | --- | |
| 241 | - | ||
| 242 | - | ## 7. Checklist QA | |
| 243 | - | ||
| 244 | - | - [ ] Owner/admin bisa membuka dan menyimpan kedua setting; user biasa tidak melihat/mendapat 403. | |
| 245 | - | - [ ] Simpan toggle tanpa mengubah field lain → cek `GET /projects/:id` masih memuat `basis`, `category`, `milestone_id`, `partner_company_id`, `closing_approver_id`, `priority`. | |
| 246 | - | - [ ] `deadline_reminder_days` = null → tidak ada email deadline. | |
| 247 | - | - [ ] `deadline_reminder_days` = 0 → email terkirim pada hari deadline untuk checker/approver yang belum merespons, subject `Due today: ...`. | |
| 248 | - | - [ ] `deadline_reminder_days` = 2 → email baru terkirim 2 hari setelah deadline, subject `Past due: ...`. | |
| 249 | - | - [ ] Tidak ada email sebelum deadline terlewati, berapa pun nilai setting-nya. | |
| 250 | - | - [ ] `disable_download_when_open` = true → download form transmittal `IN_APPROVAL` menghasilkan 409 `TRANSMITTAL_DOWNLOAD_LOCKED` dengan pesan yang benar. | |
| 251 | - | - [ ] `disable_download_when_open` = true → download form transmittal `APPROVED` tetap berhasil. | |
| 252 | - | - [ ] `disable_download_when_open` = true → download dokumen beranotasi (`POST /api/export/room/:roomId/download`) untuk revisi yang ada di transmittal `IN_CHECKING` menghasilkan 409. | |
| 253 | - | - [ ] `disable_download_when_open` = true → unduhan mpp/autodesk (`GET /document/uploads/projects/...?transmittal_id=<id>`) menghasilkan 403 `TRANSMITTAL_DOWNLOAD_LOCKED`. | |
| 254 | - | - [ ] `disable_download_when_open` = true → unduhan dokumen yang sama **tanpa** `transmittal_id` (dari Drive) tetap 200. | |
| 255 | - | - [ ] `disable_download_when_open` = true → download/export dari Drive tidak pernah memanggil pengecekan transmittal (tidak ada latensi tambahan). | |
| 256 | - | - [ ] `disable_download_when_open` = true → preview dokumen (halaman hasil ekstraksi) tetap tampil normal. | |
| 257 | - | - [ ] `disable_download_when_open` = true → hasil antrean dokumen lama (`document-downloads/:downloadId/file`, `record_resource = room`) ikut 409. | |
| 258 | - | - [ ] `disable_download_when_open` = true → unduh ulang lewat `export/record/:exportId/download` ikut 409. | |
| 259 | - | - [ ] `disable_download_when_open` = false → semua download di atas kembali normal. | |
| 260 | - | - [ ] Preview lampiran approval/komentar dan tanda tangan tetap tampil meski setting aktif. | |
| 261 | - | - [ ] Export laporan transmittal (`/reports/transmittal-report/export`) tetap berhasil meski setting aktif. | |
| 262 | - | - [ ] Ubah toggle, lalu langsung ulangi uji download tanpa menunggu (tidak ada cache). | |
| 263 | - | - [ ] Project lama dengan duplikat flat di `settings` tidak kehilangan key setelah disimpan. | |
| 264 | - | ||
| 265 | - | --- | |
| 266 | - | ||
| 267 | - | ## 8. Yang belum ada di scope ini | |
| 268 | - | ||
| 269 | - | - Belum ada UI di web. Semua kontrol di dokumen ini masih perlu dibuat. | |
| 270 | - | - Email pengingat memakai template sementara `transmittal.deadline.reminder` (product `consdoc`, shell consdoc). Desain final dari tim design belum masuk; variabel template sudah tetap, jadi penggantian desain tidak mengubah data yang dikirim. | |
| 271 | - | - Pengiriman email bersifat _at-least-once_, bukan _exactly-once_. Duplikat hanya mungkin terjadi jika proses mati tepat setelah provider email menerima pesan. Untuk keperluan UI ini tidak berpengaruh. | |
| 272 | - | - Belum ada notifikasi in-app untuk reminder; hanya email. | |
| 273 | - | - `/public` dan `/uploads` di service transmittal belum punya auth sama sekali. Setting ini hanya menambah pengecekan status, bukan menutup akses anonim ke aset. Kalau suatu saat butuh proteksi nyata, perlu pekerjaan authz aset terpisah (dan FE harus mengirim kredensial saat memuat gambar/lampiran). | |
| 274 | - | ||
| 275 | - | --- | |
| 276 | - | ||
| 277 | - | ## 9. Catatan deploy (untuk koordinasi, bukan pekerjaan FE) | |
| 278 | - | ||
| 279 | - | - Migration baru: `transmittal_reminder_outbox` (modules_transmittal) dan `email_queue.idempotency_key` (module-notification). Jalankan `prisma migrate deploy` di kedua service. | |
| 280 | - | - Template `transmittal.deadline.reminder#consdoc` perlu di-seed di database notification sebelum cron pertama. | |
| 281 | - | - `@module/contracts` sudah di-bump ke `c29b305` di modules_base, modules_transmittal, dan module-notification. | |
| 282 | - | ||
| 283 | - | --- | |
| 284 | - | ||
| 285 | - | ## 10. Referensi commit | |
| 286 | - | ||
| 287 | - | | Repo | Commit | | |
| 288 | - | | --------------------- | ------------------------------------------------------------------------------------ | | |
| 289 | - | | `module-contracts` | `c29b305` feat(contracts): add bulk project settings RPC and email idempotency key | | |
| 290 | - | | `module-contracts` | `a27e594` feat(transmittal): expose a document download policy check | | |
| 291 | - | | `module-contracts` | `d2d8a51` feat(transmittal): allow scoping the download check to one transmittal | | |
| 292 | - | | `modules_base` | `845d7df` feat(projects): expose transmittal settings and a bulk settings read | | |
| 293 | - | | `modules_base` | `bcd9d53` chore(contracts): bump @module/contracts to c29b305 | | |
| 294 | - | | `modules_transmittal` | `7d4baa7` feat(transmittal): gate form downloads and remind unanswered checkers | | |
| 295 | - | | `modules_transmittal` | `10df93c` chore(contracts): bump @module/contracts to c29b305 | | |
| 296 | - | | `modules_transmittal` | `6cbb429` refactor(transmittal): drop the attachment byte gate | | |
| 297 | - | | `modules_transmittal` | `d274963` feat(transmittal): answer document download policy over gRPC | | |
| 298 | - | | `modules_transmittal` | `00a053e` chore(contracts): bump @module/contracts to a27e594 | | |
| 299 | - | | `modules_transmittal` | `823e951` feat(transmittal): scope the download check to one transmittal | | |
| 300 | - | | `modules_transmittal` | `b07e776` chore(contracts): bump @module/contracts to d2d8a51 | | |
| 301 | - | | `modules_transmittal` | `53600a2` perf(transmittal): drop the download gate caches | | |
| 302 | - | | `modules_annotation` | `bf521ea` feat(annotation): gate annotated-pdf downloads behind the transmittal lock | | |
| 303 | - | | `modules_annotation` | `89f7253` chore(contracts): bump @module/contracts to a27e594 | | |
| 304 | - | | `modules_annotation` | `51cce5c` refactor(annotation): scope the export gate by transmittal id | | |
| 305 | - | | `modules_annotation` | `13cff8d` chore(contracts): bump @module/contracts to d2d8a51 | | |
| 306 | - | | `modules_annotation` | `a2b66ca` perf(annotation): drop the export gate cache | | |
| 307 | - | | `modules_document` | `11dd1ec` feat(document): gate raw file bytes behind the transmittal lock | | |
| 308 | - | | `modules_document` | `d1b605e` chore(contracts): bump @module/contracts to a27e594 | | |
| 309 | - | | `modules_document` | `36e41b1` refactor(document): scope the file gate by transmittal id | | |
| 310 | - | | `modules_document` | `01f0468` chore(contracts): bump @module/contracts to d2d8a51 | | |
| 311 | - | | `modules_document` | `0fa7e5e` perf(document): drop the file gate cache | | |
| 312 | - | | `module-notification` | `399345d` feat(email): dedupe template emails by caller idempotency key | | |
| 313 | - | | `module-notification` | `35e7836` chore(contracts): bump @module/contracts to c29b305 | | |
| 314 | - | ||
| 315 | - | Catatan: commit `12ba173` (gate lampiran statis) sudah dibatalkan oleh `6cbb429` — jangan dipakai sebagai acuan. | |
135dika / HANDOVER-FE-transmittal-settings.md
Last active 2 days ago
135dika revised this gist 2 days ago · 9a2c1eb
1 file changed, 77 deletions
135dika revised this gist 2 days ago · e93b438
1 file changed, 144 insertions, 21 deletions
Diff is too large to be shown
135dika revised this gist 4 days ago · 98df5cd
1 file changed, 1 insertion, 38 deletions
| @@ -189,41 +189,4 @@ Catatan desain: gate ini **bukan** batas keamanan, hanya penguncian kenyamanan. | |||
| 189 | 189 | ||
| 190 | 190 | ### Latensi efek | |
| 191 | 191 | ||
| 192 | - | Keputusan gate di-cache 60 detik per transmittal (form) dan per revisi (dokumen). Setelah toggle diubah, efeknya bisa terasa hingga ~1 menit. Kalau QA menguji "matikan lalu langsung download", tunggu atau ulangi setelah 60 detik. | |
| 193 | - | ||
| 194 | - | --- | |
| 195 | - | ||
| 196 | - | ## 5. Setting B — `deadline_reminder_days` | |
| 197 | - | ||
| 198 | - | **Label yang disarankan:** "Kirim pengingat H-… sebelum deadline" | |
| 199 | - | **Tipe kontrol:** number input (integer), boleh 0 | |
| 200 | - | **Tersedia di:** pengaturan project, hanya untuk owner/admin | |
| 201 | - | ||
| 202 | - | Semantik: | |
| 203 | - | ||
| 204 | - | - Nilai `0` = fitur mati (default). | |
| 205 | - | - Nilai `N > 0` = kirim email pengingat ketika sisa waktu menuju deadline ≤ N hari. N juga mencakup transmittal yang sudah lewat deadline. | |
| 206 | - | - Cron berjalan **setiap hari 08:00 WIB**, hanya untuk transmittal berstatus `OPEN` / `IN_CHECKING` / `IN_APPROVAL` dan belum closed. | |
| 207 | - | - Satu email per pasangan (**approval node, user, due date**). Tidak ada email berulang tiap hari untuk deadline yang sama. | |
| 208 | - | - Penerima: semua checker dan approver pada node tersebut yang belum merespons. Kalau user sudah merespons sebelum email dikirim, email dibatalkan saat itu juga. | |
| 209 | - | - Kalau transmittal selesai/ditutup sebelum email dikirim, email dibatalkan. | |
| 210 | - | ||
| 211 | - | Tidak ada UI tambahan selain input angka. Tidak ada endpoint untuk melihat riwayat reminder. | |
| 212 | - | ||
| 213 | - | Saran validasi FE: integer, `0 <= N <= 365`. Backend menolak di luar itu dengan 400. | |
| 214 | - | ||
| 215 | - | --- | |
| 216 | - | ||
| 217 | - | ## 6. Validasi & pesan error | |
| 218 | - | ||
| 219 | - | `PATCH` project memvalidasi `settings`: | |
| 220 | - | ||
| 221 | - | | Field | Aturan | Contoh tidak valid | | |
| 222 | - | |---|---|---| | |
| 223 | - | | `disable_download_when_open` | boolean | `"true"` (string) → 400 | | |
| 224 | - | | `deadline_reminder_days` | integer, 0..365, atau `null` | `-1`, `366`, `1.5` → 400 | | |
| 225 | - | | `transmittal_settings` | object | `[]`, `"on"` → 400 | | |
| 226 | - | ||
| 227 | - | Balasan 400 memakai format validasi Zod yang sudah dipakai endpoint project lain. Tidak ada kode error khusus. | |
| 228 | - | ||
| 229 | - | Hindari menulis nilai di luar rentang lewat jalur lain (script, migrasi). Nilai rusak bisa membuat pembacaan settings gagal di service lain. | |
| 192 | + | Tidak ada cache. Setiap download yang di-gate membaca status transmittal dan setting project saat itu juga, jadi toggle dan penutupan transmittal langsung berlaku pada request berikutnya. Biayanya satu lookup transmittal + satu RPC settings per klik download — aksi manusia, bukan trafik panas. | |
135dika revised this gist 4 days ago · c4ea174
1 file changed, 1 insertion, 1 deletion
| @@ -185,7 +185,7 @@ Penanganan FE: | |||
| 185 | 185 | - "Open in new tab" untuk file dokumen ikut 403 (URL sama dengan tombol download). Ini disengaja. | |
| 186 | 186 | - Preview lampiran tidak berubah, jadi tidak perlu penanganan `onerror` baru. | |
| 187 | 187 | ||
| 188 | - | Catatan desain: gate ini **bukan** batas keamanan, hanya penguncian kenyamanan. Kalau settings atau RPC antar-service gagal, backend memilih mengizinkan download (fail-open). Jangan andalkan ini untuk kontrol akses. (`/public` dan `/uploads` memang belum punya auth — pekerjaan terpisah.) | |
| 188 | + | Catatan desain: gate ini **bukan** batas keamanan, hanya penguncian kenyamanan. Kalau settings atau RPC antar-service gagal, backend memilih mengizinkan download (fail-open). Parameter `transmittal_id` juga bisa dihilangkan atau diganti dengan id transmittal lain; gate sengaja opt-in supaya Drive tidak ikut tersentuh. Jangan andalkan ini untuk kontrol akses. (`/public` dan `/uploads` memang belum punya auth — pekerjaan terpisah.) | |
| 189 | 189 | ||
| 190 | 190 | ### Latensi efek | |
| 191 | 191 | ||
135dika revised this gist 4 days ago · 6970958
1 file changed, 2 insertions, 4 deletions
| @@ -126,10 +126,10 @@ Permukaan download yang ikut diblokir (hanya aksi yang punya tombol download khu | |||
| 126 | 126 | | Unduh ulang hasil export | `GET /api/export/record/:exportId/download` | | |
| 127 | 127 | | Byte file dokumen asli (mpp/autodesk, open-in-new-tab) | `GET {document}/uploads/projects/{projectId}/documents/{documentId}/{file}` | | |
| 128 | 128 | ||
| 129 | - | Dokumen dianggap terikat transmittal lewat relasi revisi. Kalau satu revisi dipakai di beberapa transmittal, download diblokir bila **ada** transmittal yang masih open/review. | |
| 130 | - | ||
| 131 | 129 | Gate hanya aktif untuk request yang membawa `transmittal_id` (lihat bagian berikut). Unduhan dokumen yang sama dari Drive tidak berubah. | |
| 132 | 130 | ||
| 131 | + | Transmittal yang dievaluasi = transmittal yang id-nya dikirim FE. Kalau satu revisi dipakai di beberapa transmittal, transmittal lain tidak memengaruhi hasil. | |
| 132 | + | ||
| 133 | 133 | ### Wajib dari FE: kirim `transmittal_id` | |
| 134 | 134 | ||
| 135 | 135 | Drive dan transmittal memakai URL/endpoint yang sama, jadi FE harus mengirim id transmittal yang sedang dibuka. Nilainya sudah tersedia di preview: `document.transmittal.id` (`preview-document.types.ts:58-62`). Tanpa parameter itu, backend mengizinkan (perilaku lama). | |
| @@ -144,8 +144,6 @@ Tambahkan `?transmittal_id=<id>` pada: | |||
| 144 | 144 | ||
| 145 | 145 | Endpoint form transmittal (`request-download`, `transmittal-form/:transmittalId/download`, dan file antreannya) tidak perlu parameter tambahan: route itu memang hanya dipakai transmittal. | |
| 146 | 146 | ||
| 147 | - | Konsekuensi: kalau satu revisi dipakai di beberapa transmittal, yang dievaluasi hanya transmittal yang id-nya dikirim — bukan yang lain. | |
| 148 | - | ||
| 149 | 147 | Tidak terpengaruh (memang begitu desainnya): | |
| 150 | 148 | ||
| 151 | 149 | - Preview lampiran approval/komentar (`ApprovalPanel`, `PreviewCommentsPanel`, `ClosingTransmitter`, halaman inbox) — semuanya `window.open` ke file statis, bukan tombol download. Klik kanan → Save as tetap bisa; itu diterima. | |
135dika revised this gist 4 days ago · bc8a830
1 file changed, 68 insertions, 9 deletions
| @@ -16,6 +16,8 @@ Backend sudah jalan di staging. FE perlu menambah 2 kontrol di form pengaturan p | |||
| 16 | 16 | | Baca settings | `GET /api/projects/:id` | member project | | |
| 17 | 17 | | Ubah settings | `PATCH /api/projects/:projectId` | `base.project.update` (owner/admin) | | |
| 18 | 18 | | Request download form transmittal | `POST /api/projects/:projectId/reports/transmittal-form/:transmittalId/request-download` | `transmittal.download.create` | | |
| 19 | + | | Download form PDF langsung | `GET /api/projects/:projectId/reports/transmittal-form/:transmittalId/download` | `transmittal.report.read` | | |
| 20 | + | | Lampiran transmittal (byte statis) | `{transmittal}/public/transmittals/...` (URL yang sudah dipakai sekarang) | tidak ada guard terpisah | | |
| 19 | 21 | ||
| 20 | 22 | Tidak ada endpoint baru. Settings tetap dikirim lewat `PATCH` project yang sudah ada. | |
| 21 | 23 | ||
| @@ -100,19 +102,62 @@ Contoh payload benar: | |||
| 100 | 102 | ||
| 101 | 103 | ## 4. Setting A — `disable_download_when_open` | |
| 102 | 104 | ||
| 103 | - | **Label yang disarankan:** "Blokir download form transmittal selama masih diproses" | |
| 105 | + | **Label yang disarankan:** "Blokir download transmittal selama masih diproses" | |
| 104 | 106 | **Tipe kontrol:** toggle/switch | |
| 105 | 107 | **Tersedia di:** pengaturan project, hanya untuk owner/admin (`base.project.update`) | |
| 106 | 108 | ||
| 107 | - | Perilaku backend saat `true`: | |
| 109 | + | Perilaku backend saat `true` — berlaku untuk form transmittal dan dokumen yang terikat padanya: | |
| 108 | 110 | ||
| 109 | - | - Download form transmittal (`request-download`) ditolak hanya untuk transmittal dengan status `OPEN`, `IN_CHECKING`, `IN_APPROVAL` (status "masih dalam review"). | |
| 110 | - | - Semua status lain tetap bisa didownload: `DRAFT`, `SCHEDULED`, `SENT`, `REJECTED`, `REJECTED_WITH_NOTES`, `REJECTED_RESUBMISSION`, `APPROVED`, `APPROVED_WITH_NOTES`, `ARCHIVE`. | |
| 111 | - | - Transmittal yang sudah closed (`is_closed` / `effective_is_closed`) tetap bisa didownload, apa pun statusnya. | |
| 112 | - | - Download dokumen lain (file biasa, lampiran) tidak terpengaruh. Yang diblokir hanya form transmittal. | |
| 111 | + | - Diblokir hanya saat transmittal berstatus `OPEN`, `IN_CHECKING`, `IN_APPROVAL` ("masih dalam review"). | |
| 112 | + | - Semua status lain tetap bisa diunduh: `DRAFT`, `SCHEDULED`, `SENT`, `REJECTED`, `REJECTED_WITH_NOTES`, `REJECTED_RESUBMISSION`, `APPROVED`, `APPROVED_WITH_NOTES`, `ARCHIVE`. | |
| 113 | + | - Transmittal yang sudah closed (`is_closed` / `effective_is_closed`) tetap bisa diunduh, apa pun statusnya. | |
| 114 | + | - Status efektif menang atas status dasar (`effective_status` / `effective_is_closed`), sama seperti logika status lain di aplikasi. | |
| 115 | + | ||
| 116 | + | Permukaan download yang ikut diblokir (hanya aksi yang punya tombol download khusus): | |
| 117 | + | ||
| 118 | + | | Permukaan | Endpoint | | |
| 119 | + | |---|---| | |
| 120 | + | | Minta download form (antrean) | `POST /api/projects/:projectId/reports/transmittal-form/:transmittalId/request-download` | | |
| 121 | + | | Download form PDF langsung | `GET /api/projects/:projectId/reports/transmittal-form/:transmittalId/download` | | |
| 122 | + | | Ambil hasil antrean form | `GET /api/projects/:projectId/document-downloads/:downloadId/file` (hanya bila `record_resource = transmittal`) | | |
| 123 | + | | Download dokumen beranotasi (langsung) | `POST /api/export/room/:roomId/download` | | |
| 124 | + | | Minta download dokumen beranotasi (antrean) | `POST /api/export/room/:roomId/request-download` | | |
| 125 | + | | Ambil hasil antrean dokumen | `GET /api/projects/:projectId/document-downloads/:downloadId/file` (annotation, `record_resource = room`) | | |
| 126 | + | | Unduh ulang hasil export | `GET /api/export/record/:exportId/download` | | |
| 127 | + | | Byte file dokumen asli (mpp/autodesk, open-in-new-tab) | `GET {document}/uploads/projects/{projectId}/documents/{documentId}/{file}` | | |
| 128 | + | ||
| 129 | + | Dokumen dianggap terikat transmittal lewat relasi revisi. Kalau satu revisi dipakai di beberapa transmittal, download diblokir bila **ada** transmittal yang masih open/review. | |
| 130 | + | ||
| 131 | + | Gate hanya aktif untuk request yang membawa `transmittal_id` (lihat bagian berikut). Unduhan dokumen yang sama dari Drive tidak berubah. | |
| 132 | + | ||
| 133 | + | ### Wajib dari FE: kirim `transmittal_id` | |
| 134 | + | ||
| 135 | + | Drive dan transmittal memakai URL/endpoint yang sama, jadi FE harus mengirim id transmittal yang sedang dibuka. Nilainya sudah tersedia di preview: `document.transmittal.id` (`preview-document.types.ts:58-62`). Tanpa parameter itu, backend mengizinkan (perilaku lama). | |
| 136 | + | ||
| 137 | + | Tambahkan `?transmittal_id=<id>` pada: | |
| 138 | + | ||
| 139 | + | 1. `POST /api/export/room/:roomId/download` — download dokumen beranotasi (langsung). | |
| 140 | + | 2. `POST /api/export/room/:roomId/request-download` — minta download beranotasi (antrean). | |
| 141 | + | 3. `GET /api/export/record/:exportId/download` — unduh ulang hasil export. | |
| 142 | + | 4. `GET /api/projects/:projectId/document-downloads/:downloadId/file` — ambil hasil antrean, hanya untuk unduhan yang dimulai dari layar transmittal. | |
| 143 | + | 5. `GET {document}/uploads/projects/...` — tombol download mpp/autodesk dan open-in-new-tab dari preview transmittal. | |
| 144 | + | ||
| 145 | + | Endpoint form transmittal (`request-download`, `transmittal-form/:transmittalId/download`, dan file antreannya) tidak perlu parameter tambahan: route itu memang hanya dipakai transmittal. | |
| 146 | + | ||
| 147 | + | Konsekuensi: kalau satu revisi dipakai di beberapa transmittal, yang dievaluasi hanya transmittal yang id-nya dikirim — bukan yang lain. | |
| 148 | + | ||
| 149 | + | Tidak terpengaruh (memang begitu desainnya): | |
| 150 | + | ||
| 151 | + | - Preview lampiran approval/komentar (`ApprovalPanel`, `PreviewCommentsPanel`, `ClosingTransmitter`, halaman inbox) — semuanya `window.open` ke file statis, bukan tombol download. Klik kanan → Save as tetap bisa; itu diterima. | |
| 152 | + | - File tanda tangan. | |
| 153 | + | - Artefak turunan dokumen: halaman hasil ekstraksi dan preview (`.../{timestamp}_{hash}/...`), jadi viewer tetap jalan. | |
| 154 | + | - Export laporan/report transmittal (`/reports/transmittal-report/export`). | |
| 155 | + | - Dokumen di luar transmittal dan aset project lain. | |
| 113 | 156 | ||
| 114 | 157 | ### Error baru yang harus ditangani | |
| 115 | 158 | ||
| 159 | + | Endpoint API mengembalikan 409: | |
| 160 | + | ||
| 116 | 161 | ``` | |
| 117 | 162 | HTTP 409 | |
| 118 | 163 | { | |
| @@ -122,17 +167,31 @@ HTTP 409 | |||
| 122 | 167 | } | |
| 123 | 168 | ``` | |
| 124 | 169 | ||
| 170 | + | Byte file statis (baris terakhir tabel) mengembalikan **403** dengan isi yang sama: | |
| 171 | + | ||
| 172 | + | ``` | |
| 173 | + | HTTP 403 | |
| 174 | + | { | |
| 175 | + | "statusCode": 403, | |
| 176 | + | "error": "TRANSMITTAL_DOWNLOAD_LOCKED", | |
| 177 | + | "message": "Download is disabled while this transmittal is still open." | |
| 178 | + | } | |
| 179 | + | ``` | |
| 180 | + | ||
| 125 | 181 | Penanganan FE: | |
| 126 | 182 | ||
| 127 | - | - Branch pada `status === 409 && error === 'TRANSMITTAL_DOWNLOAD_LOCKED'`. | |
| 183 | + | - Branch pada `error === 'TRANSMITTAL_DOWNLOAD_LOCKED'`, jangan cek status code saja — 409 untuk endpoint API, 403 untuk byte file statis. | |
| 128 | 184 | - Tampilkan pesan yang jelas, bukan toast generik "gagal download". Sarankan: "Download dinonaktifkan selama transmittal masih diproses. Hubungi admin project untuk mengubah pengaturan." | |
| 129 | 185 | - Jangan retry otomatis. Ini keputusan konfigurasi, bukan error sementara. | |
| 186 | + | - Unduhan mpp/autodesk memakai `fetch` langsung ke URL statis; cek `response.ok` dan baca JSON error di atas sebelum menampilkan toast. | |
| 187 | + | - "Open in new tab" untuk file dokumen ikut 403 (URL sama dengan tombol download). Ini disengaja. | |
| 188 | + | - Preview lampiran tidak berubah, jadi tidak perlu penanganan `onerror` baru. | |
| 130 | 189 | ||
| 131 | - | Catatan desain: gate ini **bukan** batas keamanan, hanya penguncian kenyamanan. Kalau pembacaan settings gagal, backend memilih mengizinkan download (fail-open). Jangan andalkan ini untuk kontrol akses. | |
| 190 | + | Catatan desain: gate ini **bukan** batas keamanan, hanya penguncian kenyamanan. Kalau settings atau RPC antar-service gagal, backend memilih mengizinkan download (fail-open). Jangan andalkan ini untuk kontrol akses. (`/public` dan `/uploads` memang belum punya auth — pekerjaan terpisah.) | |
| 132 | 191 | ||
| 133 | 192 | ### Latensi efek | |
| 134 | 193 | ||
| 135 | - | Settings di-cache 60 detik di service transmittal. Setelah toggle diubah, efeknya bisa terasa hingga ~1 menit. Kalau QA menguji "matikan lalu langsung download", tunggu atau ulangi setelah 60 detik. | |
| 194 | + | Keputusan gate di-cache 60 detik per transmittal (form) dan per revisi (dokumen). Setelah toggle diubah, efeknya bisa terasa hingga ~1 menit. Kalau QA menguji "matikan lalu langsung download", tunggu atau ulangi setelah 60 detik. | |
| 136 | 195 | ||
| 137 | 196 | --- | |
| 138 | 197 | ||
135dika revised this gist 4 days ago · 74ce615
1 file changed, 1 insertion, 45 deletions
| @@ -169,48 +169,4 @@ Saran validasi FE: integer, `0 <= N <= 365`. Backend menolak di luar itu dengan | |||
| 169 | 169 | ||
| 170 | 170 | Balasan 400 memakai format validasi Zod yang sudah dipakai endpoint project lain. Tidak ada kode error khusus. | |
| 171 | 171 | ||
| 172 | - | Hindari menulis nilai di luar rentang lewat jalur lain (script, migrasi). Nilai rusak bisa membuat pembacaan settings gagal di service lain. | |
| 173 | - | ||
| 174 | - | --- | |
| 175 | - | ||
| 176 | - | ## 7. Checklist QA | |
| 177 | - | ||
| 178 | - | - [ ] Owner/admin bisa membuka dan menyimpan kedua setting; user biasa tidak melihat/mendapat 403. | |
| 179 | - | - [ ] Simpan toggle tanpa mengubah field lain → cek `GET /projects/:id` masih memuat `basis`, `category`, `milestone_id`, `partner_company_id`, `closing_approver_id`, `priority`. | |
| 180 | - | - [ ] `deadline_reminder_days` = 0 → tidak ada email pengingat. | |
| 181 | - | - [ ] `disable_download_when_open` = true → download form transmittal `IN_APPROVAL` menghasilkan 409 `TRANSMITTAL_DOWNLOAD_LOCKED` dengan pesan yang benar. | |
| 182 | - | - [ ] `disable_download_when_open` = true → download form transmittal `APPROVED` tetap berhasil. | |
| 183 | - | - [ ] `disable_download_when_open` = true → download dokumen/lampiran lain tidak terpengaruh. | |
| 184 | - | - [ ] Ubah toggle, tunggu ≤ 60 detik, ulangi uji download (cache). | |
| 185 | - | - [ ] Project lama dengan duplikat flat di `settings` tidak kehilangan key setelah disimpan. | |
| 186 | - | ||
| 187 | - | --- | |
| 188 | - | ||
| 189 | - | ## 8. Yang belum ada di scope ini | |
| 190 | - | ||
| 191 | - | - Belum ada UI di web. Semua kontrol di dokumen ini masih perlu dibuat. | |
| 192 | - | - Email pengingat memakai template sementara `transmittal.deadline.reminder` (product `consdoc`, shell consdoc). Desain final dari tim design belum masuk; variabel template sudah tetap, jadi penggantian desain tidak mengubah data yang dikirim. | |
| 193 | - | - Pengiriman email bersifat *at-least-once*, bukan *exactly-once*. Duplikat hanya mungkin terjadi jika proses mati tepat setelah provider email menerima pesan. Untuk keperluan UI ini tidak berpengaruh. | |
| 194 | - | - Belum ada notifikasi in-app untuk reminder; hanya email. | |
| 195 | - | ||
| 196 | - | --- | |
| 197 | - | ||
| 198 | - | ## 9. Catatan deploy (untuk koordinasi, bukan pekerjaan FE) | |
| 199 | - | ||
| 200 | - | - Migration baru: `transmittal_reminder_outbox` (modules_transmittal) dan `email_queue.idempotency_key` (module-notification). Jalankan `prisma migrate deploy` di kedua service. | |
| 201 | - | - Template `transmittal.deadline.reminder#consdoc` perlu di-seed di database notification sebelum cron pertama. | |
| 202 | - | - `@module/contracts` sudah di-bump ke `c29b305` di modules_base, modules_transmittal, dan module-notification. | |
| 203 | - | ||
| 204 | - | --- | |
| 205 | - | ||
| 206 | - | ## 10. Referensi commit | |
| 207 | - | ||
| 208 | - | | Repo | Commit | | |
| 209 | - | |---|---| | |
| 210 | - | | `module-contracts` | `c29b305` feat(contracts): add bulk project settings RPC and email idempotency key | | |
| 211 | - | | `modules_base` | `845d7df` feat(projects): expose transmittal settings and a bulk settings read | | |
| 212 | - | | `modules_base` | `bcd9d53` chore(contracts): bump @module/contracts to c29b305 | | |
| 213 | - | | `modules_transmittal` | `7d4baa7` feat(transmittal): gate form downloads and remind unanswered checkers | | |
| 214 | - | | `modules_transmittal` | `10df93c` chore(contracts): bump @module/contracts to c29b305 | | |
| 215 | - | | `module-notification` | `399345d` feat(email): dedupe template emails by caller idempotency key | | |
| 216 | - | | `module-notification` | `35e7836` chore(contracts): bump @module/contracts to c29b305 | | |
| 172 | + | Hindari menulis nilai di luar rentang lewat jalur lain (script, migrasi). Nilai rusak bisa membuat pembacaan settings gagal di service lain. | |
135dika revised this gist 4 days ago · ac54742
1 file changed, 216 insertions
Diff is too large to be shown