Like 0

Revision e93b4387a32b7ab8b3910ab589bc88c15277008b

HANDOVER-FE-transmittal-settings.md Raw

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.

{
  "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 atau null, default null (= mati). 0 = email tepat pada hari deadline, N = N hari setelah deadline.
  • 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:

{
  "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:

{
  "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:

{
  "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=<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.


5. Setting B — deadline_reminder_days

Label yang disarankan: "Kirim email setelah deadline terlewati (hari)" Tipe kontrol: number input (integer), boleh 0 Tersedia di: pengaturan project, hanya untuk owner/admin

Semantik:

  • Kosong / null = fitur mati (default).
  • Nilai 0 = email dikirim tepat pada hari deadline.
  • Nilai N > 0 = email dikirim N hari setelah deadline.
  • Ini bukan pengingat sebelum deadline. Checker/approver tetap bisa merespons setelah deadline, jadi emailnya berisi pemberitahuan bahwa respons masih kosong.
  • Cron berjalan setiap hari 08:00 WIB, hanya untuk transmittal berstatus OPEN / IN_CHECKING / IN_APPROVAL dan belum closed.
  • Satu email per pasangan (approval node, user, due date). Tidak ada email berulang tiap hari untuk deadline yang sama.
  • Penerima: semua checker dan approver pada node tersebut yang belum merespons. Kalau user sudah merespons sebelum email dikirim, email dibatalkan saat itu juga.
  • Kalau transmittal selesai/ditutup sebelum email dikirim, email dibatalkan.

Tidak ada UI tambahan selain input angka. Tidak ada endpoint untuk melihat riwayat reminder.

Saran validasi FE: integer, 0 <= N <= 365. Backend menolak di luar itu dengan 400.

Isi email: subject Due today: <ref> needs your <role> response atau Past due: <ref> needs your <role> response; badan email menyebut deadline sudah terlewati dan respons masih diterima.


6. Validasi & pesan error

PATCH project memvalidasi settings:

Field Aturan Contoh tidak valid
disable_download_when_open boolean "true" (string) → 400
deadline_reminder_days integer, 0..365, atau null -1, 366, 1.5 → 400
transmittal_settings object [], "on" → 400

Balasan 400 memakai format validasi Zod yang sudah dipakai endpoint project lain. Tidak ada kode error khusus.

Hindari menulis nilai di luar rentang lewat jalur lain (script, migrasi). Nilai rusak bisa membuat pembacaan settings gagal di service lain.


7. Checklist QA

  • Owner/admin bisa membuka dan menyimpan kedua setting; user biasa tidak melihat/mendapat 403.
  • Simpan toggle tanpa mengubah field lain → cek GET /projects/:id masih memuat basis, category, milestone_id, partner_company_id, closing_approver_id, priority.
  • deadline_reminder_days = null → tidak ada email deadline.
  • deadline_reminder_days = 0 → email terkirim pada hari deadline untuk checker/approver yang belum merespons, subject Due today: ....
  • deadline_reminder_days = 2 → email baru terkirim 2 hari setelah deadline, subject Past due: ....
  • Tidak ada email sebelum deadline terlewati, berapa pun nilai setting-nya.
  • disable_download_when_open = true → download form transmittal IN_APPROVAL menghasilkan 409 TRANSMITTAL_DOWNLOAD_LOCKED dengan pesan yang benar.
  • disable_download_when_open = true → download form transmittal APPROVED tetap berhasil.
  • disable_download_when_open = true → download dokumen beranotasi (POST /api/export/room/:roomId/download) untuk revisi yang ada di transmittal IN_CHECKING menghasilkan 409.
  • disable_download_when_open = true → unduhan mpp/autodesk (GET /document/uploads/projects/...?transmittal_id=<id>) menghasilkan 403 TRANSMITTAL_DOWNLOAD_LOCKED.
  • disable_download_when_open = true → unduhan dokumen yang sama tanpa transmittal_id (dari Drive) tetap 200.
  • disable_download_when_open = true → download/export dari Drive tidak pernah memanggil pengecekan transmittal (tidak ada latensi tambahan).
  • disable_download_when_open = true → preview dokumen (halaman hasil ekstraksi) tetap tampil normal.
  • disable_download_when_open = true → hasil antrean dokumen lama (document-downloads/:downloadId/file, record_resource = room) ikut 409.
  • disable_download_when_open = true → unduh ulang lewat export/record/:exportId/download ikut 409.
  • disable_download_when_open = false → semua download di atas kembali normal.
  • Preview lampiran approval/komentar dan tanda tangan tetap tampil meski setting aktif.
  • Export laporan transmittal (/reports/transmittal-report/export) tetap berhasil meski setting aktif.
  • Ubah toggle, lalu langsung ulangi uji download tanpa menunggu (tidak ada cache).
  • Project lama dengan duplikat flat di settings tidak kehilangan key setelah disimpan.

8. Yang belum ada di scope ini

  • Belum ada UI di web. Semua kontrol di dokumen ini masih perlu dibuat.
  • 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.
  • 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.
  • Belum ada notifikasi in-app untuk reminder; hanya email.
  • /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).

9. Catatan deploy (untuk koordinasi, bukan pekerjaan FE)

  • Migration baru: transmittal_reminder_outbox (modules_transmittal) dan email_queue.idempotency_key (module-notification). Jalankan prisma migrate deploy di kedua service.
  • Template transmittal.deadline.reminder#consdoc perlu di-seed di database notification sebelum cron pertama.
  • @module/contracts sudah di-bump ke c29b305 di modules_base, modules_transmittal, dan module-notification.

10. Referensi commit

Repo Commit
module-contracts c29b305 feat(contracts): add bulk project settings RPC and email idempotency key
module-contracts a27e594 feat(transmittal): expose a document download policy check
module-contracts d2d8a51 feat(transmittal): allow scoping the download check to one transmittal
modules_base 845d7df feat(projects): expose transmittal settings and a bulk settings read
modules_base bcd9d53 chore(contracts): bump @module/contracts to c29b305
modules_transmittal 7d4baa7 feat(transmittal): gate form downloads and remind unanswered checkers
modules_transmittal 10df93c chore(contracts): bump @module/contracts to c29b305
modules_transmittal 6cbb429 refactor(transmittal): drop the attachment byte gate
modules_transmittal d274963 feat(transmittal): answer document download policy over gRPC
modules_transmittal 00a053e chore(contracts): bump @module/contracts to a27e594
modules_transmittal 823e951 feat(transmittal): scope the download check to one transmittal
modules_transmittal b07e776 chore(contracts): bump @module/contracts to d2d8a51
modules_transmittal 53600a2 perf(transmittal): drop the download gate caches
modules_annotation bf521ea feat(annotation): gate annotated-pdf downloads behind the transmittal lock
modules_annotation 89f7253 chore(contracts): bump @module/contracts to a27e594
modules_annotation 51cce5c refactor(annotation): scope the export gate by transmittal id
modules_annotation 13cff8d chore(contracts): bump @module/contracts to d2d8a51
modules_annotation a2b66ca perf(annotation): drop the export gate cache
modules_document 11dd1ec feat(document): gate raw file bytes behind the transmittal lock
modules_document d1b605e chore(contracts): bump @module/contracts to a27e594
modules_document 36e41b1 refactor(document): scope the file gate by transmittal id
modules_document 01f0468 chore(contracts): bump @module/contracts to d2d8a51
modules_document 0fa7e5e perf(document): drop the file gate cache
module-notification 399345d feat(email): dedupe template emails by caller idempotency key
module-notification 35e7836 chore(contracts): bump @module/contracts to c29b305

Catatan: commit 12ba173 (gate lampiran statis) sudah dibatalkan oleh 6cbb429 — jangan dipakai sebagai acuan.