# Handover FE — Transmittal Project Settings Dua setting baru di level project (`Projects.settings`) yang memengaruhi perilaku transmittal: 1. **Blokir download form transmittal** selama transmittal masih dalam proses review. 2. **Reminder deadline** (dalam hari) ke checker/approver yang belum merespons, dikirim email oleh cron 08:00 WIB. Backend sudah jalan di staging. FE perlu menambah 2 kontrol di form pengaturan project dan menangani satu error baru saat download. --- ## 1. Endpoint yang dipakai | Kebutuhan | Endpoint | Permission | |---|---|---| | Baca settings | `GET /api/projects/:id` | member project | | Ubah settings | `PATCH /api/projects/:projectId` | `base.project.update` (owner/admin) | | Request download form transmittal | `POST /api/projects/:projectId/reports/transmittal-form/:transmittalId/request-download` | `transmittal.download.create` | | Download form PDF langsung | `GET /api/projects/:projectId/reports/transmittal-form/:transmittalId/download` | `transmittal.report.read` | | Lampiran transmittal (byte statis) | `{transmittal}/public/transmittals/...` (URL yang sudah dipakai sekarang) | tidak ada guard terpisah | Tidak ada endpoint baru. Settings tetap dikirim lewat `PATCH` project yang sudah ada. Versi API dikirim lewat header (custom versioning), bukan di path. Pakai base URL + header yang sudah dipakai web saat ini. --- ## 2. Bentuk data di `settings` Settings adalah **satu kolom JSON**, bukan kolom per-setting. Grup baru: `transmittal_settings`. ```json { "transmittal_settings": { "disable_download_when_open": true, "deadline_reminder_days": 3 } } ``` Aturan: - `disable_download_when_open` — `boolean`, default `false`. - `deadline_reminder_days` — `integer 0..365`, default `0` (= mati). `null` diterima, artinya unset. - Dua-duanya opsional. Grup `transmittal_settings` opsional. - Key lain di dalam `settings` **wajib dipertahankan**. Backend memakai `catchall`, jadi key yang tidak dikenal tidak ditolak dan tidak dibuang. ### Bentuk data produksi (penting) Row lama menyimpan nilai yang sama dua kali: di dalam grup dan di top-level. Contoh nyata: ```json { "approver_revoke_time": 2, "approver_revoke_time_unit": "days", "transmittal_settings": { "approver_revoke_time": 2, "approver_revoke_time_unit": "days" } } ``` Backend membaca **nested dulu**, lalu fallback ke key dengan nama sama di top-level. Jadi penulisan baru cukup ke dalam `transmittal_settings`. Duplikat flat lama boleh dibiarkan. --- ## 3. Jebakan: `PATCH` mengganti seluruh kolom `settings` `settings` adalah JSON column dan di-update **replace, bukan merge**. Kalau FE mengirim: ```json { "settings": { "transmittal_settings": { "disable_download_when_open": true } } } ``` maka seluruh key lain (`basis`, `category`, `milestone_id`, `partner_company_id`, `closing_approver_id`, `priority`, `approver_revoke_time`, dst.) **hilang**. Kontrak untuk FE: - Ambil `settings` dari `GET /projects/:id`. - Gabungkan perubahan secara lokal: `settings.transmittal_settings = { ...settings.transmittal_settings, ...perubahan }`. - Kirim objek `settings` hasil gabungan secara utuh di `PATCH`. - Jangan mengirim `transmittal_settings` saja. Contoh payload benar: ```json { "settings": { "basis": "Berdasarkan arahan PT Danantara Asset Management", "category": "CONSULTANT", "priority": "MEDIUM", "milestone_id": "bf083b10-53f8-492c-bc02-c731608d7a62", "partner_company_id": "12219e63-8762-4a7b-a8f4-70cb9705ffd8", "closing_approver_id": "33b51ecb-62c2-4ef7-af2a-fc55a00e75bb", "basis_of_implementation": "Berdasarkan arahan PT Danantara Asset Management", "transmittal_settings": { "disable_download_when_open": true, "deadline_reminder_days": 3 } } } ``` --- ## 4. Setting A — `disable_download_when_open` **Label yang disarankan:** "Blokir download transmittal selama masih diproses" **Tipe kontrol:** toggle/switch **Tersedia di:** pengaturan project, hanya untuk owner/admin (`base.project.update`) Perilaku backend saat `true` — berlaku untuk form transmittal dan dokumen yang terikat padanya: - Diblokir hanya saat transmittal berstatus `OPEN`, `IN_CHECKING`, `IN_APPROVAL` ("masih dalam review"). - Semua status lain tetap bisa diunduh: `DRAFT`, `SCHEDULED`, `SENT`, `REJECTED`, `REJECTED_WITH_NOTES`, `REJECTED_RESUBMISSION`, `APPROVED`, `APPROVED_WITH_NOTES`, `ARCHIVE`. - Transmittal yang sudah closed (`is_closed` / `effective_is_closed`) tetap bisa diunduh, apa pun statusnya. - Status efektif menang atas status dasar (`effective_status` / `effective_is_closed`), sama seperti logika status lain di aplikasi. Permukaan download yang ikut diblokir (hanya aksi yang punya tombol download khusus): | Permukaan | Endpoint | |---|---| | Minta download form (antrean) | `POST /api/projects/:projectId/reports/transmittal-form/:transmittalId/request-download` | | Download form PDF langsung | `GET /api/projects/:projectId/reports/transmittal-form/:transmittalId/download` | | Ambil hasil antrean form | `GET /api/projects/:projectId/document-downloads/:downloadId/file` (hanya bila `record_resource = transmittal`) | | Download dokumen beranotasi (langsung) | `POST /api/export/room/:roomId/download` | | Minta download dokumen beranotasi (antrean) | `POST /api/export/room/:roomId/request-download` | | Ambil hasil antrean dokumen | `GET /api/projects/:projectId/document-downloads/:downloadId/file` (annotation, `record_resource = room`) | | Unduh ulang hasil export | `GET /api/export/record/:exportId/download` | | Byte file dokumen asli (mpp/autodesk, open-in-new-tab) | `GET {document}/uploads/projects/{projectId}/documents/{documentId}/{file}` | Gate hanya aktif untuk request yang membawa `transmittal_id` (lihat bagian berikut). Unduhan dokumen yang sama dari Drive tidak berubah. Transmittal yang dievaluasi = transmittal yang id-nya dikirim FE. Kalau satu revisi dipakai di beberapa transmittal, transmittal lain tidak memengaruhi hasil. ### Wajib dari FE: kirim `transmittal_id` 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). Tambahkan `?transmittal_id=` pada: 1. `POST /api/export/room/:roomId/download` — download dokumen beranotasi (langsung). 2. `POST /api/export/room/:roomId/request-download` — minta download beranotasi (antrean). 3. `GET /api/export/record/:exportId/download` — unduh ulang hasil export. 4. `GET /api/projects/:projectId/document-downloads/:downloadId/file` — ambil hasil antrean, hanya untuk unduhan yang dimulai dari layar transmittal. 5. `GET {document}/uploads/projects/...` — tombol download mpp/autodesk dan open-in-new-tab dari preview transmittal. Endpoint form transmittal (`request-download`, `transmittal-form/:transmittalId/download`, dan file antreannya) tidak perlu parameter tambahan: route itu memang hanya dipakai transmittal. Tidak terpengaruh (memang begitu desainnya): - 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. - File tanda tangan. - Artefak turunan dokumen: halaman hasil ekstraksi dan preview (`.../{timestamp}_{hash}/...`), jadi viewer tetap jalan. - Export laporan/report transmittal (`/reports/transmittal-report/export`). - Dokumen di luar transmittal dan aset project lain. ### Error baru yang harus ditangani Endpoint API mengembalikan 409: ``` HTTP 409 { "statusCode": 409, "error": "TRANSMITTAL_DOWNLOAD_LOCKED", "message": "Download is disabled while this transmittal is still open." } ``` Byte file statis (baris terakhir tabel) mengembalikan **403** dengan isi yang sama: ``` HTTP 403 { "statusCode": 403, "error": "TRANSMITTAL_DOWNLOAD_LOCKED", "message": "Download is disabled while this transmittal is still open." } ``` Penanganan FE: - Branch pada `error === 'TRANSMITTAL_DOWNLOAD_LOCKED'`, jangan cek status code saja — 409 untuk endpoint API, 403 untuk byte file statis. - Tampilkan pesan yang jelas, bukan toast generik "gagal download". Sarankan: "Download dinonaktifkan selama transmittal masih diproses. Hubungi admin project untuk mengubah pengaturan." - Jangan retry otomatis. Ini keputusan konfigurasi, bukan error sementara. - Unduhan mpp/autodesk memakai `fetch` langsung ke URL statis; cek `response.ok` dan baca JSON error di atas sebelum menampilkan toast. - "Open in new tab" untuk file dokumen ikut 403 (URL sama dengan tombol download). Ini disengaja. - Preview lampiran tidak berubah, jadi tidak perlu penanganan `onerror` baru. 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.) ### Latensi efek 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.