Featured

    Featured Posts

  • dimana kehidupan
  • disitulah jawaban

Cara VM VirtualBox Nyala Otomatis Saat Restart

Server kantor itu sudah lama punya satu kebiasaan: begitu listrik mati lalu hidup lagi, komputernya memang menyala sendiri, tapi VM di dalamnya tetap tidur. Layanan di dalam VM itu baru hidup lagi kalau login ke server, lalu menyalakan VM-nya manual.

Makanya suatu hari saya sengaja merestart server itu pake remote, cuma buat membuktikan satu hal: kalau tidak ada siapa pun yang login, VM-nya mau menyala sendiri atau tetap tidur begitu saja.

Hasil tesnya ada di akhir catatan ini, tapi yang membuat catatan ini layak ditulis justru perjalanan ke sananya: ada tampilan yang sempat saya baca salah, ada tombol yang nyaris saya pencet sampai VM-nya mati beneran, dan ada kode tanggal aneh dari tahun 1999. Saya tulis semuanya di sini, supaya kalau suatu hari saya harus memasang hal yang sama di mesin lain, saya tinggal baca ulang, tidak perlu mengulang bingungnya dari nol.

Daftar Isi

  • 1. Masalah Lama: Hostnya Nyala, VM-nya Masih Tidur
  • 2. Kenapa Bukan Auto-Login Saja
  • 3. Bikin Task-nya di Task Scheduler
  • 4. Bagian yang Sempat Bikin Bingung
  • 5. Tes Pertama: Klik Run
  • 6. Jebakan Waktu Mau Nutup Konsol
  • 7. Tes Sebenarnya: Restart dari Rumah
  • 8. Hal yang Saya Pelajari
  • 9. Penutup

1. Masalah Lama: Hostnya Nyala, VM-nya Masih Tidur

Di kantor ada satu PC server yang menjalankan Windows. Di dalam Windows itu ada VirtualBox, dan di dalam VirtualBox ada satu VM Ubuntu yang jadi mesin produksinya. Isinya beberapa layanan yang dipakai sehari-hari: otomasi, dashboard, dan beberapa layanan pendukung lain. Di catatan ini VM-nya saya sebut saja server-ubuntu.

Susunan bertingkat seperti itu ada konsekuensinya setiap kali listrik bermasalah. Komputer fisiknya sebenarnya sudah diatur supaya menyala sendiri begitu listrik kembali. Jadi bagian pertama selalu beres: hostnya hidup lagi.

Masalahnya, VM di dalamnya tidak ikut menyala. Dia tetap mati sampai ada orang yang datang, login ke Windows, membuka VirtualBox, dan menyalakan VM-nya secara manual. Selama belum ada yang melakukan itu, semua layanan di dalamnya ikut mati, padahal mesin fisiknya sudah hidup dari tadi.

Cara manualnya juga ada aturan anehnya, ini yang dulu diajarkan ke saya waktu pertama kali belajar: setelah VM dinyalakan, akan muncul jendela konsol berwarna hitam, dan jendela itu tidak boleh ditutup. Kalau ditutup, VM-nya ikut mati. Jadi selama bertahun-tahun polanya begitu. Nyalakan manual, lalu jendelanya dibiarkan saja, jangan disentuh.

2. Kenapa Bukan Auto-Login Saja

Ide paling gampang yang sempat terpikir oleh saya: buat saja Windows-nya login otomatis setiap habis restart, lalu taruh VM-nya di daftar program yang berjalan setelah login. Selesai, kan?

Ternyata cara itu saya urungkan. Alasannya keamanan. Auto-login itu artinya password akun disimpan di Windows dalam bentuk yang bisa dibaca kembali. Server ini bukan server yang diam sendirian di pojok ruangan, dia terhubung ke internet dan layanannya bisa diakses dari luar. Rasanya sayang saja, pintu depannya sudah dikunci, tapi kuncinya digantung di sebelah pintu.

Jadi rencananya saya ubah. Halaman login Windows tetap ada dan tetap mengunci seperti biasa. Yang dibuat menyala otomatis adalah VM-nya saja, berjalan sebelum ada yang login, dan berjalan headless, alias tanpa jendela sama sekali. Tidak ada lagi jendela hitam yang tidak boleh ditutup itu.

Alatnya ternyata sudah bawaan Windows sendiri: Task Scheduler. Tidak perlu memasang program tambahan apa pun.

3. Bikin Task-nya di Task Scheduler

Di Task Scheduler, saya pilih Create Task. Bukan Create Basic Task, karena yang versi penuh ini punya pilihan jalan sebelum login itu.

VirtualBox Nyala Otomatis


Tab General

Name AutoStart server-ubuntu
Akun yang menjalankan SERVERKU\Administrator, yaitu akun pemilik VM-nya
Pilihan jalan Run whether user is logged on or not
Do not store password Tidak dicentang
Run with highest privileges Dicentang

Ada dua hal penting di tab ini. Pertama, akunnya jangan diganti ke SYSTEM. Daftar VM di VirtualBox itu tersimpan mengikuti akun pemiliknya. Kalau task-nya dijalankan sebagai SYSTEM, dia tidak akan menemukan VM-nya, dan kegagalannya diam-diam saja, tidak ada pesan error yang jelas.

Kedua, pilihan Do not store password memang sengaja tidak saya centang. Justru password-nya perlu disimpan, karena itu syaratnya task ini bisa berjalan sebelum ada yang login. Nanti waktu task-nya disimpan, Windows akan meminta password akun itu satu kali. Itu memang bagian dari prosesnya, bukan error.

Bagian Configure for di tab ini masih tertulis Windows Vista / Windows Server 2008 pada punya saya. Itu saya biarkan saja, ternyata tidak berpengaruh apa-apa ke jalan atau tidaknya task ini.

Tab Triggers

Begin the task At startup
Delay task for 30 seconds (dicentang)
Enabled Dicentang

Jeda 30 detik itu saya pasang supaya Windows-nya selesai dulu mengurus dirinya sendiri waktu booting, baru VM-nya dinyalakan. Pilihan lain di tab ini, seperti pengulangan jadwal dan tanggal kedaluwarsa, semuanya saya biarkan tidak dicentang.

Tab Actions

Isinya satu perintah saja. Ini bagian intinya:

Action Start a program
Program/script C:\Program Files\Oracle\VirtualBox\VBoxManage.exe
Add arguments startvm "server-ubuntu" --type headless
Program : C:\Program Files\Oracle\VirtualBox\VBoxManage.exe
Argumen : startvm "server-ubuntu" --type headless

Kata headless di argumen itu yang membuat VM-nya menyala tanpa jendela. Nama VM di dalam tanda kutip harus persis sama dengan nama yang tertulis di VirtualBox, huruf besar kecilnya juga. Salah satu huruf saja, perintahnya gagal.

4. Bagian yang Sempat Bikin Bingung

Di dua tab terakhir ini saya sempat bolak-balik bertanya, karena tampilannya tidak sama persis dengan yang saya bayangkan.

Tab Conditions: Centang yang Abu-abu

Di bagian Power ada dua pilihan yang bertumpuk. Pilihan induknya, Start the task only if the computer is on AC power, sudah saya hilangkan centangnya. Tapi pilihan di bawahnya, Stop if the computer switches to battery power, masih kelihatan ada centangnya.

Ternyata pilihan kedua itu warnanya abu-abu alias tidak aktif. Dia anak dari pilihan induknya. Induknya mati, anaknya ikut tidak berlaku, walaupun gambar centangnya masih kelihatan. Jadi di tab Conditions ini intinya: semua syarat saya matikan. Task-nya harus jalan tanpa menunggu komputer diam dulu, tanpa menunggu listrik tertentu, dan tanpa menunggu jaringan tertentu.

Tab Settings: Tiga Pilihan yang Artinya Samar

Run task as soon as possible after a scheduled start is missed saya centang. Artinya kalau momen startup-nya sempat terlewat, task-nya tetap dijalankan begitu ada kesempatan, tidak dibiarkan hilang begitu saja.

Stop the task if it runs longer than saya hilangkan centangnya. Saya tidak mau ada batas waktu yang berpotensi menghentikan task penahan VM ini.

Yang paling membuat saya bertanya adalah pilihan paling bawah:

If the task is already running, then the following rule applies: Do not start a new instance

Artinya ternyata sederhana. Kalau task ini sedang berjalan, misalnya dia sedang menyalakan VM waktu komputer baru hidup, lalu di saat yang sama ada pemicu kedua, entah dari saya yang iseng mengklik Run atau dari trigger yang terulang, Windows tidak akan menjalankan perintahnya dua kali. Ini pengaman supaya VirtualBox tidak disuruh menyalakan VM yang sama dua kali barengan, yang ujungnya cuma menghasilkan pesan error bahwa VM-nya sudah terkunci atau sudah berjalan.

Satu lagi, If the running task does not end when requested, force it to stop, pada punya saya tercentang dan saya biarkan. Itu hanya berlaku kalau ada orang yang sengaja mengakhiri task ini dan task-nya menolak berhenti. Dia tidak akan mematikan VM secara tiba-tiba selama semua berjalan normal.

5. Tes Pertama: Klik Run

Task selesai dibuat dan statusnya Ready. Tapi status Ready itu baru separuh bukti. Task-nya harus dicoba dijalankan beneran.

Saya klik kanan task-nya, lalu pilih Run. Di daftar Task Scheduler, statusnya berubah menjadi Running. Awalnya saya mengira status itu hanya sebentar lalu kembali Ready. Ternyata tidak, dia tetap Running. Dan itu memang benar: status Running itu artinya proses penahan VM-nya sedang hidup. Selama VM-nya menyala, task-nya akan tertulis Running.

Di sinilah saya sempat dibuat bingung oleh kolom Last Run Result. Isinya tertulis begini:

The task has not yet run. (0x41303)

Kolom tanggalnya juga aneh, tertulis 30 November 1999. Kedengarannya seperti error, apalagi ada kode segala. Ternyata bukan. Kolom Last Run itu mencatat menjalankan sebelumnya yang sudah selesai. Ini menjalankan saya yang pertama, dan dia belum selesai karena VM-nya memang sedang ditahan hidup. Karena belum ada catatan sebelumnya, Windows mengisinya dengan nilai kosongnya dia: kode 0x41303 dan tanggal tahun 1999 itu. Jadi selama statusnya Running, kolom itu abaikan saja.

Bukti penentunya bukan di Task Scheduler, tapi di VirtualBox-nya langsung:

VM VirtualBox Nyala Otomatis Saat Restart

VM server-ubuntu statusnya Running dengan panah hijau, sementara VM satunya tetap Powered Off seperti yang memang saya mau. Tes pertama lulus.

Catatan penting yang saya tulis besar-besar buat diri saya sendiri: jangan pernah mengklik End pada task ini. Mengakhiri task itu sama dengan membunuh proses penahan VM-nya secara paksa. Persis seperti menutup jendela hitam zaman dulu, cuma sekarang jendelanya tidak kelihatan.

6. Jebakan Waktu Mau Nutup Konsol

Walaupun VM-nya headless, layarnya sebenarnya tetap ada, cuma disembunyikan. Kalau saya mau melihat terminal Ubuntu-nya seperti dulu, saya tinggal membuka VirtualBox, memilih VM-nya, lalu mengklik tombol Show. Jendela konsol hitamnya akan muncul, lengkap dengan prompt login seperti biasa.

Nah, jebakannya muncul waktu saya mau menutup jendela konsol itu:

Cara VM VirtualBox Nyala Otomatis Saat Restart

Muncul dialog Close Virtual Machine dengan empat pilihan. Dan pilihan yang sudah terpilih dari sananya adalah yang paling bawah: Power off the machine. Kalau waktu itu saya langsung mengklik OK tanpa membaca, VM yang baru saja susah payah dinyalakan itu akan mati lagi saat itu juga, dan matinya paksa pula.

Pilihan yang benar untuk menutup konsol tanpa mematikan VM adalah yang paling atas: Continue running in the background. Pilih itu, baru klik OK. Jendela konsolnya tertutup, VM-nya tetap berjalan di belakang layar.

Jadi sekarang aturannya berubah total dari zaman dulu. Menutup jendela VirtualBox Manager itu aman. Menutup jendela Task Scheduler itu aman. Bahkan disconnect dari remote dan membiarkan Windows-nya terkunci di halaman login juga aman. Yang berbahaya cuma dua: mengklik End pada task-nya, dan memilih Power off pada dialog penutup konsol itu.

7. Tes Sebenarnya: Restart dari Rumah

Tes klik Run tadi baru membuktikan perintahnya benar. Yang belum terbukti adalah trigger-nya: apakah dia benar-benar berjalan waktu Windows booting, sebelum ada yang login?

Buat ngetes itu, urutannya saya buat hati-hati. VM-nya tidak saya matikan paksa. Saya buka konsolnya lewat tombol Show tadi, login ke Ubuntu, lalu menjalankan perintah ini:

sudo poweroff

Di layar konsol kelihatan semua layanannya berhenti satu-satu dengan tulisan OK, termasuk Docker-nya. Itu tanda matinya rapi, bukan dicabut paksa. Setelah itu status VM di VirtualBox berubah menjadi Powered Off. Barulah host Windows-nya saya restart, dari rumah, lewat remote desktop.

Sambungan remote-nya putus selama servernya mati, itu normal. Beberapa menit kemudian saya sambung lagi, dan yang muncul adalah ini:

Cara VM VirtualBox Nyala Otomatis Saat Restart

Server kantornya berhenti persis di halaman login Windows. Terkunci, tidak ada yang login, dan saya memang sengaja tidak login dulu.

Dalam keadaan belum login itu, saya buka alamat salah satu layanan yang jalan di dalam VM, dari browser di rumah. Contohnya di catatan ini saya tulis otomasi.contohdomain.my.id. Dan halaman login layanannya muncul dengan normal:

Cara VM VirtualBox Nyala Otomatis

Buat saya itu bukti finalnya, dan tidak bisa dibantah. Layanannya hidup, padahal Windows-nya masih duduk di halaman login dan belum ada satu orang pun yang masuk. Berarti runtutannya benar-benar berjalan sendiri: host menyala, jeda 30 detik, task berjalan sebelum login, VM menyala headless, lalu semua layanan di dalamnya ikut naik.

Setelah bukti itu dapat, barulah saya login ke Windows-nya buat melihat sendiri status VM-nya Running di VirtualBox, sekadar penutup.

8. Hal yang Saya Pelajari

  • Autostart VM dan auto-login Windows itu dua hal yang berbeda jauh. Yang pertama bisa didapat tanpa mengorbankan yang kedua.
  • Task Scheduler itu jauh lebih bisa diandalkan daripada kelihatannya, asal tiga tab intinya diisi dengan benar: akun pemilik VM, trigger At startup, dan perintah headless-nya.
  • Status task yang tertulis Running terus-menerus itu belum tentu error. Buat task penahan VM seperti ini, Running justru tanda dia sedang bekerja.
  • Kode 0x41303 dan tanggal tahun 1999 di kolom Last Run itu bukan kerusakan. Itu nilai kosong dari Windows untuk task yang belum pernah menyelesaikan satu putaran pun.
  • Dialog penutup konsol VirtualBox itu jebakan beneran, karena pilihan bawaannya adalah Power off. Baca dulu sebelum mengklik OK.
  • Tes yang meyakinkan itu bukan yang semua langkahnya saya awasi sambil login. Tes yang meyakinkan adalah yang saya biarkan berjalan sendiri dalam keadaan terkunci, lalu saya periksa hasilnya dari luar.
  • Satu utang catatan buat diri sendiri: kalau suatu hari password Administrator Windows ini diganti, password yang tersimpan di dalam task ini harus diisi ulang. Kalau tidak, autostart-nya akan gagal diam-diam.

9. Penutup

Sekarang pola lama itu sudah pensiun. Tidak ada lagi cerita hostnya sudah menyala tapi layanannya masih mati menunggu ada orang datang menyalakan VM secara manual. Dan tidak ada lagi jendela hitam yang harus dijaga jangan sampai tertutup.

Habis mati lampu pun urutannya sekarang sama dengan tes restart kemarin: listrik kembali, host menyala sendiri, VM-nya menyusul sebelum ada yang login, layanannya naik sendiri. Saya tinggal memeriksa dari mana saja.

Semoga catatan ini bermanfaat, terutama buat saya sendiri kalau nanti lupa dan harus memasang hal yang sama di mesin yang lain.


Disclaimer

Catatan belajar pribadi, bukan tutorial resmi. Proses memasang dan mengetesnya dibantu AI; nama VM, akun, dan alamat layanan di tulisan dan gambar sudah diganti dengan contoh dummy.

Nyoba Deploy Website ke Cloudflare Pages, Gratis dan Nggak Ribet

Ceritanya lagi bikin satu halaman sederhana berisi kumpulan tautan kantor. Satu file, isinya daftar link layanan yang sering dipakai, biar tidak usah mencari satu-satu lagi. Halaman ini saya sebut saja halaman portal.

Halaman itu saya taruh di GitHub dan saya tayangkan pakai GitHub Pages. Selama repo-nya publik, semua aman-aman saja. Alamatnya memang masih alamat bawaan GitHub, tapi yang penting bisa dibuka.

Masalahnya muncul dari keinginan yang kedengarannya sepele: saya pengin repo-nya saya privat saja. Ternyata keputusan kecil itu membuat websitenya mati. Catatan ini tentang bagaimana saya memindahkannya ke Cloudflare Pages, memasang domain sendiri, dan akhirnya repo bisa privat tanpa mematikan situsnya.

Daftar Isi

  • 1. Pas Diprivat, Malah Mati
  • 2. Bedanya Workers dan Pages
  • 3. Urutan Pindah yang Benar
  • 4. Langkah-langkah Pindahnya
  • 5. Pasang Domain Sendiri
  • 6. Baru Privat, Lalu Tes Ulang
  • 7. Setelah Pindah: Commit Langsung Tayang
  • 8. Hal yang Saya Pelajari
  • 9. Penutup

1. Pas Diprivat, Malah Mati

Suatu hari saya mengubah repo portal ini dari publik menjadi privat. Saya pikir urusannya selesai di situ, tinggal pengaturan tampil atau tidak tampilnya kode.

Ternyata tidak. Beberapa saat setelah saya privat, alamat GitHub Pages-nya tidak bisa dibuka lagi. Situsnya ikut mati.

Setelah saya cari tahu, sebabnya begini: di akun GitHub gratis, GitHub Pages hanya menerbitkan situs dari repo yang publik. Repo privat yang bisa ditayangkan itu fasilitas berbayar, masuknya ke paket Pro.

Jadi pilihannya waktu itu ada tiga. Repo tetap publik, atau bayar, atau pindah hosting ke tempat yang memperbolehkan repo privat. Saya pilih yang ketiga: pindah ke Cloudflare Pages. Gratisnya dapat, repo tetap bisa privat, dan domain sendiri juga bisa dipasang di tempat yang sama.

Pelajaran pertama dari kejadian ini: jangan privat dulu sebelum rumah barunya siap. Urutannya kebalik sedikit saja, situsnya mati beneran.

2. Bedanya Workers dan Pages

Sebelum lanjut, bagian ini yang sempat membuat saya bingung lumayan lama. Di Cloudflare ada dua nama yang mirip-mirip: Workers dan Pages. Keduanya ada di menu yang sama, tapi kerjaannya beda.

Cara gampang saya mengingatnya begini:

  • Pages itu etalase. Tempat menaruh file yang sudah jadi: HTML, CSS, JavaScript, gambar. Ada pengunjung datang, file itu diberikan apa adanya. Tidak ada yang berpikir, tidak ada yang mengolah.
  • Workers itu petugas yang berpikir dulu. Ini kode atau program yang berjalan setiap ada permintaan datang. Dia bisa meneruskan permintaan ke tempat lain, memeriksa sesuatu, mengubah jawaban, atau memanggil layanan lain sebelum menjawab.

Kalau dibuat tabel, kira-kira begini:

Pembanding Cloudflare Pages Cloudflare Workers
Isinya File statis yang sudah jadi Kode yang berjalan tiap ada permintaan
Bisa mengolah / meneruskan permintaan? Tidak, hanya mengirim file Bisa, termasuk jadi proxy atau perantara
Cocok untuk Halaman profil, landing page, kumpulan link, blog statis API, proxy ke Apps Script, aturan redirect, pemeriksaan akses
Contoh punya saya Halaman portal kantor di catatan ini Sistem antrean konsultasi di catatan saya yang lain

Dari tabel itu kesimpulannya jadi jelas buat saya. Halaman portal isinya cuma daftar link, file-nya sudah jadi, tidak ada yang perlu diolah di tengah jalan. Jadi Pages saja sudah cukup, memakai Workers malah berlebihan.

Sebaliknya, sistem antrean konsultasi saya yang lain memang butuh Workers, karena di sana ada perantara yang meneruskan data ke Apps Script. Beda kebutuhan, beda alatnya.

3. Urutan Pindah yang Benar

Ini bagian terpenting dari seluruh catatan ini. Urutannya tidak boleh ditukar:

Urutan Migrasi Custom Domain
1. Pindah hosting dulu ke Cloudflare Pages (repo masih publik)
↓
2. Tes alamat sementara dari Pages sampai benar-benar hidup
↓
3. Pasang domain sendiri dan tes lagi
↓
4. BARU privat-kan repo GitHub
↓
5. Tes ulang semuanya

Intinya satu: privat itu langkah terakhir. Kalau dilakukan di awal, yang terjadi ya seperti pengalaman saya sebelumnya, situsnya mati sebelum rumah barunya jadi.

4. Langkah-langkah Pindahnya

Langkah 1: Kasih Izin ke Repo Portal

Waktu pertama mencoba, repo portal saya tidak muncul di daftar pilihan Cloudflare. Sempat bingung, ternyata sebabnya sederhana: aplikasi GitHub milik Cloudflare itu hanya diizinkan mengakses repo tertentu saja.

Di GitHub, saya buka pengaturan aplikasi Cloudflare Workers and Pages, lalu di bagian akses repository saya pilih Only select repositories. Di daftarnya waktu itu baru ada repo konsultasi saya. Repo portalnya saya tambahkan di situ, sampai daftarnya jadi dua repo, lalu saya simpan.

Repo konsultasi jangan dicabut dari daftar. Kalau dicabut, proyek yang lama bisa ikut terganggu. Menambah saja, bukan mengganti.

Langkah 2: Masuk ke Jalur Pages

Di dashboard Cloudflare, menunya sekarang ada di Compute → Workers & Pages, lalu klik Create application.

Di halaman itu ada tombol besar bertuliskan Continue with GitHub. Nah, tombol besar itu bukan yang saya klik. Tombol itu jalur Workers yang baru, nanti langkah-langkah di dalamnya berbeda.

Yang saya klik adalah tautan kecil di bagian bawah halaman: Continue to Pages. Di dekatnya ada tulisan tentang legacy Pages workflow. Memang kecil dan gampang terlewat, saya sendiri awalnya hampir salah klik yang besar.

Setelah itu pilih Import an existing Git repository, lalu pilih repo portalnya dan klik Begin setup.

Langkah 3: Isi Build Settings

Karena halaman portal ini file statis polos tanpa framework apa pun, isiannya sangat sederhana:

Setting Cloudflare Pages
Project name portal-kantor (sesuai nama repo)
Production branch main
Framework preset None
Build command (kosong, tidak diisi apa pun)
Build output directory (kosong / root, karena file HTML ada di folder utama repo)

Bagian lanjutan seperti root directory dan environment variables saya biarkan saja, tidak saya buka-buka. Setelah sama semua, klik Save and Deploy dan tunggu satu sampai tiga menit.

Kalau berhasil, muncul tulisan Success dan satu alamat sementara berakhiran .pages.dev. Punya saya alamatnya tidak persis sama dengan nama proyek, ada tambahan huruf acak di belakangnya, karena nama polosnya ternyata sudah dipakai orang lain lebih dulu. Tidak masalah, itu wajar dan memang bukan alamat utamanya. Alamat itu cuma buat ngetes.

Alamat sementara itu saya klik dan saya buka. Halamannya muncul lengkap, judul dan daftar link-nya sama seperti aslinya. Berarti pindahan hostingnya sah, bukan cuma tulisan sukses saja.

5. Pasang Domain Sendiri

Masih di halaman proyek Pages yang sama, saya masuk ke tab Custom domains, lalu klik Set up a custom domain.

Di kolom domain saya isi subdomain yang saya mau, polos saja tanpa awalan apa pun. Contohnya di catatan ini saya tulis portal.contohdomain.my.id.

Setelah klik Continue, Cloudflare menampilkan satu record DNS yang akan dibuatkan. Isinya saya periksa dulu baik-baik sebelum lanjut:

Type CNAME
Name portal
Content / Target nama-project-abc.pages.dev
TTL Auto

Karena domain induknya memang berada di akun Cloudflare yang sama, record itu dibuatkan otomatis. Saya tinggal klik Activate domain.

Statusnya awalnya tertulis Verifying. Saya tunggu beberapa menit, tidak saya utak-atik. Setelah itu statusnya berubah menjadi Active, dan alamat domain sendiri itu sudah bisa dibuka dengan HTTPS seperti website biasa.

Saran dari pengalaman saya: periksa target CNAME-nya sebelum klik Activate. Salah satu huruf saja di target itu, alamatnya bisa nyasar ke tempat lain.

6. Baru Privat, Lalu Tes Ulang

Sampai di titik ini rumah barunya sudah jadi dan alamat barunya sudah hidup. Barulah sekarang repo-nya saya privat, dengan hati yang jauh lebih tenang dibanding percobaan pertama.

Caranya: buka repo di GitHub, masuk ke Settings pada repo itu, gulir sampai paling bawah ke bagian Danger Zone, lalu di Change repository visibility pilih Make private dan ikuti konfirmasinya sampai selesai.

Habis itu langsung saya tes tiga alamat:

  • Domain sendiri: hidup, terbuka normal. Ini yang paling penting.
  • Alamat .pages.dev: hidup juga, isinya sama.
  • Alamat lama .github.io: 404 alias mati.

Yang terakhir itu jangan panik. Justru 404 di alamat lama adalah bukti bahwa privatnya berhasil, dan alamat lama memang sudah tidak dipakai lagi. Bedanya dengan kejadian pertama: kali ini situs utamanya tidak ikut mati, karena yang menayangkan sudah Cloudflare Pages, bukan GitHub Pages.

7. Setelah Pindah: Commit Langsung Tayang

Satu hal yang saya tanyakan ke diri sendiri setelah pindah: kalau nanti saya mengubah isi link di repo, apakah saya harus deploy ulang secara manual?

Ternyata tidak. Setiap ada commit baru ke branch main, Cloudflare Pages langsung membangun dan menayangkan ulang secara otomatis. Biasanya satu sampai dua menit sudah berubah.

Enaknya jelas: edit file di GitHub, commit, selesai. Tapi konsekuensinya juga harus diingat: commit itu artinya langsung tayang ke publik. Jadi sebelum commit, periksa dulu isinya. Jangan sampai link yang belum jadi ikut terbit.

Data isi portalnya sendiri saya taruh di satu file data terpisah, isinya daftar link seperti ini. Ini contoh dummy:

{
  "links": [
    {
      "judul": "Contoh Layanan Satu",
      "url": "https://contoh-alamat-kantor.example",
      "keterangan": "Deskripsi singkat layanan"
    },
    {
      "judul": "Contoh Layanan Dua",
      "url": "https://contoh-alamat-lain.example",
      "keterangan": "Deskripsi singkat layanan"
    }
  ]
}

Dengan cara ini, kalau cuma mau menambah atau mengubah satu link, saya cukup mengubah file data itu saja. Tidak perlu mengutak-atik halaman utamanya.

8. Hal yang Saya Pelajari

  • Privat dan publik pada repo itu bukan cuma soal terlihat atau tidak. Di GitHub gratis, pilihan itu ikut menentukan apakah situs GitHub Pages-nya bisa tayang.
  • Urutan langkah sering lebih penting daripada langkahnya sendiri. Langkah yang sama, kalau urutannya ditukar, hasilnya bisa mati total.
  • Workers dan Pages itu dua alat berbeda. Memilih yang paling sederhana dan sesuai kebutuhan biasanya lebih enak dirawat.
  • Alamat sementara dari layanan hosting itu wajar kalau tidak persis sama dengan nama proyek. Alamat yang disebar ke orang tetap domain sendiri.
  • Setelah pindah, selalu tes ulang semua alamat. Jangan menganggap berhasil hanya dari tulisan Success di dashboard.

9. Penutup

Sekarang halaman portal kantor ini sudah tayang dari alamatnya sendiri, repo-nya sudah privat, dan saya tidak perlu waswas lagi setiap mau mengubah pengaturan repo.

Semoga catatan ini bermanfaat, terutama buat saya sendiri kalau nanti lupa dan harus memindahkan halaman statis yang lain suatu hari nanti.


Disclaimer

Tulisan ini adalah catatan belajar pribadi tentang memindahkan hosting halaman portal ke Cloudflare Pages dan memasang domain sendiri, bukan tutorial resmi dan bukan panduan baku dari pihak mana pun. Saya menulis ulang pengalaman saya supaya tidak lupa, bukan karena bisa.

Prosesnya sendiri banyak dibantu AI dan diskusi bolak-balik, apalagi saat mencari sebab situsnya mati setelah repo diprivat dan saat membedakan jalur Pages dengan Workers itu. Jadi jangan dibayangkan saya sudah paham semuanya. Saya masih belajar, dan sangat mungkin ada bagian di catatan ini yang kurang tepat atau bisa diperbaiki dengan cara yang lebih baik.

Kalau ada yang membaca dan menemukan cara yang lebih rapi, saya justru senang. Yang penting bagi saya, langkahnya tercatat dan bisa dibaca lagi saat lupa, dan semoga bermanfaat juga buat yang sedang belajar hal yang sama.

Belajar Apps Script: Pasang Domain Sendiri Pakai Cloudflare Worker

Sebelumnya sistem antrean konsultasi ini cuma jalan dari link bawaan Google Apps Script. Link-nya panjang, acak, dan jujur saja susah diingat. Saya sendiri saja kalau mau buka harus cari-cari lagi di catatan.

Dari situ muncul keinginan sederhana: pengin punya alamat sendiri yang pendek, misalnya bimbingan.contohdomain.my.id, biar gampang ditulis di pengumuman dan gampang diingat mahasiswa.

Ternyata perjalanan ke sana tidak sesederhana "tinggal ganti alamat". Catatan ini saya tulis supaya langkahnya tidak hilang, karena saya yakin suatu saat saya akan lupa dan butuh membaca ini lagi.

Daftar Isi

  • 1. Gambaran Alurnya Dulu
  • 2. Isi Dapurnya: Apps Script Jadi API
  • 3. Frontend dan Dua Worker
  • 4. Sempat Mentok: Error 405
  • 5. Sempat Bingung: Route /api yang 404
  • 6. Pasang Domain Sendiri
  • 7. Aturan Main Sistemnya
  • 8. Hal yang Saya Pelajari
  • 9. Penutup

1. Gambaran Alurnya Dulu

Sistem ini saya pecah jadi dua bagian. Bagian depan adalah halaman web yang dibuka orang. Bagian belakang adalah Apps Script yang mengurus data ke Google Sheets dan Google Drive.

Kenapa dipecah? Karena kalau halaman ikut menumpang di Apps Script, alamatnya ikut alamat Apps Script. Kalau halamannya dipisah dan ditaruh di tempat lain, alamatnya bisa pakai domain sendiri, sementara Apps Script cukup bekerja di belakang layar.

Arsitektur Sistem

Alurnya kalau ditulis pakai panah kira-kira begini:

Mahasiswa / Admin
↓ buka halaman
Frontend (domain sendiri)
↓ kirim data JSON
API Worker (proxy)
↓ teruskan permintaan
Apps Script Web App (/exec)
↓ baca / tulis
Google Sheets (database) + Google Drive (dokumen)

Halaman mahasiswanya dipakai untuk daftar antrean, cek status antrean, dan unggah dokumen. Halaman adminnya terpisah, masuknya pakai PIN, dipakai untuk memanggil antrean berikutnya, menandai selesai, mengatur jadwal, dan memeriksa dokumen yang masuk.

Alur Daftar Antrian

2. Isi Dapurnya: Apps Script Jadi API

Di Apps Script, fungsi doPost saya jadikan pintu masuk. Setiap permintaan datang membawa satu keterangan: mau melakukan apa. Di kode, keterangan itu dibaca dari isian bernama action.

Bentuk sederhananya begini:

function doPost(e) {
  var data = JSON.parse(e.postData.contents);
  var aksi = data.action;

  if (aksi === "daftar") { return daftarAntrean(data); }
  if (aksi === "cek")    { return cekAntrean(data); }
  if (aksi === "upload_dokumen") { return uploadDokumen(data); }
  if (aksi === "admin_next")     { return panggilBerikutnya(data); }

  return balasJson({ ok: false, pesan: "Action tidak dikenal" });
}

Action yang akhirnya kepakai cukup banyak. Untuk mahasiswa ada daftar, cek, upload_dokumen, dan cek_dokumen. Untuk admin ada admin_list, admin_next, admin_selesai, admin_set_jadwal, admin_list_dokumen, dan admin_update_dokumen.

Contoh permintaan cek antrean dari halaman depan bentuknya begini:

{
  "action": "cek",
  "nim": "0000000000"
}

Dan jawaban dari belakang kira-kira begini. Ini contoh dummy, bukan data asli:

{
  "ok": true,
  "pesan": "Data antrean ditemukan",
  "antrean": {
    "nomor": 7,
    "status": "menunggu"
  }
}

Databasenya bukan database beneran, melainkan Google Sheets. Isinya saya bagi jadi beberapa sheet:

ANTRIAN Isi daftar antrean: nama, NIM, waktu daftar, status.
DOKUMEN Catatan dokumen yang diunggah, file aslinya disimpan di Google Drive.
SETTING Pengaturan, termasuk PIN admin. PIN tersimpan di sheet SETTING, jangan tampilkan PIN asli di tulisan mana pun.
ADMIN dan TOKEN Pendukung untuk keperluan admin dan sesi.

Saat deploy Web App, pengaturannya saya isi: Execute as: Me dan Who has access: Anyone. Alasannya supaya mahasiswa bisa memakai halaman tanpa harus login akun Google dulu. Urusan admin tetap dijaga PIN, bukan dibuka begitu saja.

3. Frontend dan Dua Worker

Halaman depannya saya simpan di repo GitHub yang saya privat, lalu dihubungkan ke Cloudflare. Di Cloudflare, proyek ini jalan sebagai Worker, bukan sekadar tempat menaruh file.

Ada dua Worker yang bekerja:

  • Worker frontend — yang melayani halaman mahasiswa dan halaman admin saat domain dibuka.
  • Worker API — yang jadi perantara antara halaman dan Apps Script.

Kenapa perlu perantara segala? Karena halaman yang jalan di domain sendiri tidak bisa sembarangan memanggil alamat lain, ada aturan keamanan browser yang namanya CORS. Worker API ini yang menjembatani, sekaligus menyembunyikan alamat asli Apps Script dari tampilan luar.

Alamat Apps Script-nya sendiri di kode cukup ditulis seperti ini. Ini contoh yang sudah saya samarkan:

const GAS_URL = "https://script.google.com/macros/s/XXXX-contoh-xxxx/exec";

4. Sempat Mentok: Error 405

Bagian ini yang paling lama bikin saya berhenti. Halaman sudah jadi, Worker sudah jalan, tapi setiap mengirim data balasannya 405.

Setelah diurut pelan-pelan, ternyata masalahnya ada di cara Worker meneruskan kiriman ke Google. Apps Script itu kalau menerima POST, dia tidak langsung menjawab. Dia membalas dengan pengalihan, istilahnya redirect 302, ke alamat sementara.

Kesalahan saya waktu itu: Worker saya ikut-ikutan mengirim POST lagi ke alamat pengalihan itu. Di situlah Google menolak, keluarlah 405.

Solusinya ternyata sepele, tapi tidak kepikiran di awal:

POST cukup sekali saja ke alamat /exec. Setelah itu ikuti pengalihannya pakai GET, karena tiket pengalihan itu sudah membawa data POST yang kita kirim.

Potongan kodenya kira-kira begini:

async function fetchViaGas(body) {
  // 1. POST sekali saja ke /exec, jangan auto mengikuti redirect
  let res = await fetch(GAS_URL, {
    method: "POST",
    body: body,
    redirect: "manual"
  });

  // 2. Kalau dibalas redirect 302, ikuti pakai GET
  let lokasi = res.headers.get("Location");
  if (lokasi) {
    res = await fetch(lokasi, { method: "GET" });
  }

  return res;
}

Setelah bagian itu diperbaiki, kiriman cek antrean langsung dibalas 200 dengan jawaban JSON yang benar. Rasanya lega sekali, karena masalahnya bukan di Apps Script-nya, melainkan di cara meneruskannya.

5. Sempat Bingung: Route /api yang 404

Kejadian kedua tidak kalah aneh. Rencananya halaman dan API mau saya jadikan satu domain, jadi API saya pasang di route /api/* pada domain yang sama.

Dari hasil tes di luar, route itu menjawab 200 dan isinya benar. Tapi dari browser saya sendiri, yang muncul justru 404. Sudah dicoba pakai mode penyamaran, tetap sama.

Di titik itu saya berhenti memaksakan diri. Daripada sistemnya tidak jalan cuma gara-gara satu route, saya pakai jalan keluar yang lebih praktis: di kode halaman depan, alamat API-nya saya isi lengkap memakai alamat bawaan Worker API.

const CONFIG = {
  apiBase: "https://nama-api.username.workers.dev"
};

Kenapa ini aman dipakai? Dua hal. Pertama, Worker API saya beri header CORS yang membuka akses. Kedua, kiriman dari halaman saya buat memakai content type text/plain, jadi browser tidak ribet dengan pemeriksaan awal yang biasanya bikin gagal.

const headers = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type",
  "Content-Type": "application/json"
};

Sampai sekarang route satu domain itu belum saya sentuh lagi. Sistemnya sudah jalan dengan cara ini, jadi saya catat saja sebagai utang lama. Kalau nanti ada waktu dan kepala sedang dingin, baru saya tengok lagi.

6. Pasang Domain Sendiri

Domain yang saya pakai memang sudah saya tambahkan ke Cloudflare sejak awal, jadi urusan nama server tidak perlu diulang dari nol.

Langkah pasang alamatnya begini. Pertama, buka Worker frontend di dashboard Cloudflare. Kedua, masuk ke pengaturan domain dan route. Ketiga, tambahkan Custom Domain dengan subdomain yang diinginkan, contohnya bimbingan.contohdomain.my.id. Keempat, API-nya sekalian diatur jalurnya, dari pengaturan Worker API.

Setelah itu tinggal ditunggu sebentar sampai HTTPS-nya siap, lalu dicoba dibuka seperti membuka website biasa. Momen pertama kali alamat pendek itu kebuka dan halamannya muncul, itu yang bikin semua utak-atik sebelumnya terasa terbayar.

7. Aturan Main Sistemnya

Biar tidak cuma soal teknis, sekalian saya catat aturan main yang saya pasang di sistem ini:

  • Pendaftaran dibuka pukul 08.00 sampai 23.00, di luar itu formulir ditutup.
  • Satu mahasiswa hanya boleh konsultasi satu kali dalam satu minggu.
  • Antrean bisa dibuka atau ditutup dari pengaturan.
  • Dokumen yang diunggah masuk ke folder Drive yang berbeda sesuai jenis dokumennya.
  • Halaman admin hanya bisa dipakai setelah memasukkan PIN, dan PIN-nya tersimpan di sheet SETTING.

8. Hal yang Saya Pelajari

  • Alamat bawaan layanan itu tidak wajib jadi alamat yang dilihat orang. Halaman dan mesin di belakangnya bisa dipisah.
  • Redirect itu bukan error. Dalam kasus Apps Script, redirect 302 justru bagian dari alur normalnya, hanya cara mengikutinya harus benar: POST sekali, lalu GET.
  • Kalau satu jalan mentok terlalu lama, jalan keluar yang sederhana dan terbukti jalan itu lebih berharga daripada memaksakan cara yang paling rapi di atas kertas.
  • Mencatat pesan error dan langkah terakhir sebelum error itu jauh lebih membantu daripada sekadar mengingat "tadi gagal".
  • Sistem kecil seperti ini ternyata tetap butuh aturan main yang jelas, bukan cuma halaman dan tombol.

9. Penutup

Sekarang sistem antrean ini sudah bisa dibuka dari alamat sendiri yang pendek. Mahasiswa tidak perlu lagi menerima link panjang dari saya, dan saya tidak perlu lagi mencari-cari link itu tiap mau membagikan.

Semoga catatan ini bermanfaat, terutama buat saya sendiri kalau nanti lupa dan harus mengulang hal yang sama dari awal.


Disclaimer

Tulisan ini adalah catatan belajar pribadi tentang sistem antrean konsultasi dan domain sendiri, bukan tutorial resmi dan bukan panduan baku dari pihak mana pun. Saya menulis ulang pengalaman saya supaya tidak lupa, bukan karena saya sudah paham.

Prosesnya sendiri banyak dibantu AI dan diskusi bolak-balik, apalagi saat mengatasi error 405 dan route yang 404 itu. Jadi jangan dibayangkan saya sudah paham semuanya. Saya masih belajar, dan sangat mungkin ada bagian di catatan ini yang kurang tepat atau bisa diperbaiki dengan cara yang lebih baik.

Kalau ada yang membaca dan menemukan cara yang lebih rapi, saya justru senang. Yang penting bagi saya, langkahnya tercatat dan bisa dibaca lagi saat lupa, dan semoga bermanfaat juga buat yang sedang belajar hal yang sama.

Belajar n8n: Membuat Pengingat Absensi Otomatis ke Grup WhatsApp Kantor

Awalnya saya sudah pernah membuat workflow pengingat absensi menggunakan n8n. Tapi jujur saja, waktu itu proses bikinnya cukup banyak dibantu AI. Jadi meskipun workflow-nya jalan, saya tidak benar-benar paham alurnya.

Membuat Pengingat Absensi Otomatis ke Grup WhatsApp Kantor


Workflow lama yang saya buat kurang lebih seperti ini:

Cron
↓
Baca daftar pegawai
↓
Loop satu per satu
↓
Generate pesan
↓
Kirim WhatsApp personal

Karena pesannya dikirim ke masing-masing nomor pegawai, workflow-nya jadi lumayan panjang.

Nah, beberapa waktu lalu saya kepikiran untuk menyederhanakan semuanya.

Daripada kirim satu-satu ke seluruh pegawai, kenapa tidak kirim sekali saja ke grup WhatsApp kantor?

Selain lebih sederhana, proses maintenance-nya juga jauh lebih mudah.

Kondisi yang saya gunakan

Beberapa catatan setup yang saya pakai:

n8n

Self-hosted.

WhatsApp Gateway

Saya menggunakan:

GOWA

Repository:

https://github.com/aldinokemal/go-whatsapp-web-multidevice

Keamanan

Dashboard GOWA saya amankan menggunakan Cloudflare Access.

Jadi orang luar tidak bisa sembarangan membuka halaman login.

Sedangkan untuk n8n, saya tidak menggunakan URL Cloudflare.

Saya langsung mengakses GOWA menggunakan IP lokal:

http://IP_LOKAL_SERVER:PORT

Tujuannya supaya request dari n8n tidak terhalang proteksi Cloudflare.

Rencana Workflow

Targetnya sederhana:

Pagi

Senin - Jumat
07:45

Kirim ke grup kantor:

Jangan lupa absensi masuk

Sore

Senin - Kamis
16:45

Kirim:

Jangan lupa absensi pulang

Jumat

Jamnya berbeda.

Karena jam kerja kantor berbeda dengan hari biasa.

Membuat Schedule Trigger

Di node pertama saya menggunakan:

Schedule Trigger

Lalu memilih:

Trigger Interval
=
Weeks

Kemudian memilih hari:

Monday
Tuesday
Wednesday
Thursday
Friday

dan mengatur waktu sesuai kebutuhan.

Contoh:

Hour  : 07
Minute: 45

Awalnya saya sempat bingung antara menggunakan:

Days

atau

Weeks

Ternyata yang lebih pas adalah:

Weeks

karena kita bisa memilih hari tertentu dan mengecualikan Sabtu-Minggu.

Membuat Isi Pesan

Node kedua saya menggunakan:

Edit Fields

atau yang dulu dikenal sebagai:

Set

Saya membuat field baru bernama:

message

Isi contohnya:

🌞 Selamat pagi.

Jangan lupa melakukan absensi masuk.

Terima kasih.

Sederhana saja.

Yang penting pesannya tersampaikan.

Mengirim Pesan ke Grup WhatsApp

Bagian ini yang awalnya membuat saya agak bingung.

Karena sebelumnya saya mengirim ke nomor pribadi.

Sedangkan sekarang tujuannya grup WhatsApp.

Ternyata yang dibutuhkan adalah:

Group ID

Formatnya bukan nomor HP.

Kurang lebih seperti ini:

120363xxxxxxxxxxxx@g.us

Yang penting ada akhiran:

@g.us

HTTP Request ke GOWA

Node berikutnya:

HTTP Request

Konfigurasinya:

Method:
POST

URL:

http://IP_LOKAL_SERVER:PORT/send/message

Body JSON:

{
  "phone": "120363xxxxxxxxxxxx@g.us",
  "message": "{{ $json.message }}"
}

Untuk memilih device WhatsApp yang digunakan:

device_id = device_contoh

Karena di server saya ada beberapa device WhatsApp yang terhubung.

Saya ingin workflow ini menggunakan device:

device_contoh

Sempat Bingung Soal Basic Auth

Workflow lama saya menggunakan:

Basic Auth

Sedangkan workflow baru saya mencoba:

Authentication = None

Setelah dicoba ternyata tetap berjalan normal.

Karena request berasal dari jaringan lokal dan endpoint memang bisa diakses dari LAN.

Akhirnya saya memilih yang lebih sederhana:

None

Eksperimen Mention Semua Anggota Grup

Saya juga sempat mencoba:

@everyone

dengan harapan seluruh anggota grup akan terkena mention.

Pesan memang berhasil terkirim:

@everyone

tetapi tidak benar-benar menjadi mention WhatsApp.

Masih tampil sebagai teks biasa.

Untuk sementara saya biarkan saja karena tujuan utama workflow ini hanyalah pengingat absensi.

Tidak terlalu penting harus melakukan mention ke seluruh anggota grup.

Menambahkan Sistem "Hansip Lapor Error"

Sambil belajar, saya juga sekalian membuat workflow khusus untuk menangkap error.

Kalau ada workflow yang gagal, saya ingin mendapatkan laporan otomatis.

Kurang lebih seperti ini:

Error Trigger
↓
Kirim Telegram

Pesan yang saya kirim ke Telegram:

🚨 HANSIP JAGA MELAPOR!

Workflow:
...

Node:
...

Error:
...

Link:
...

Awalnya saya sempat bingung.

Workflow dibuat.

Sudah publish.

Tapi Telegram tidak masuk.

Ternyata ada satu hal yang saya pelajari:

Error Trigger tidak selalu aktif ketika saya hanya menekan tombol:

Execute Workflow

di editor.

Supaya benar-benar teruji, saya membuat workflow yang sengaja error menggunakan kode:

throw new Error("TEST HANSIP PRODUCTION")

Setelah workflow dijalankan sebagai production execution, akhirnya notifikasi Telegram berhasil masuk.

Lumayan puas juga waktu melihat "Hansip" pertama kali berhasil melapor. 😆

Hal Yang Saya Pelajari

Kalau dipikir-pikir, workflow pengingat absensi grup ternyata jauh lebih sederhana daripada workflow pengingat ke nomor personal.

Dari yang awalnya:

Cron
↓
Baca Data
↓
Loop
↓
Generate Pesan
↓
Send WA

menjadi:

Schedule Trigger
↓
Edit Fields
↓
HTTP Request

Lebih pendek.

Lebih mudah dipahami.

Dan lebih gampang dirawat kalau nanti suatu saat perlu diubah.

Penutup

Belajar n8n buat saya menarik karena sering kali yang sulit bukan menambahkan node, tetapi memahami alur datanya.

Kadang workflow bisa jalan, tetapi kita sendiri belum tentu paham kenapa bisa jalan.

Makanya saya mulai menulis catatan-catatan seperti ini.

Selain untuk dokumentasi pribadi, juga sebagai pengingat kalau beberapa bulan lagi saya lupa settingannya dan harus mengulang dari awal.


Disclaimer

Tulisan ini lebih merupakan catatan belajar daripada tutorial resmi.

Proses pembuatan workflow di atas banyak saya lakukan dengan bantuan Microsoft Copilot dan diskusi bolak-balik selama proses troubleshooting.

Jadi jangan dibayangkan saya sudah benar-benar paham semuanya. 😄

Saya masih belajar.

Kalau ada bagian yang menurut teman-teman kurang tepat, sangat mungkin memang ada yang bisa diperbaiki.

Yang penting buat saya, setiap kali mencoba sesuatu, ada catatan yang bisa dibaca lagi saat lupa, dan semoga bermanfaat juga untuk teman-teman yang sedang belajar n8n dari nol seperti saya.

Merapikan Data Inventaris Kantor dengan Google Spreadsheet

Beberapa waktu ini saya mulai belajar merapikan data inventaris kantor. Ceritanya saya kebetulan diminta untuk ikut membantu pendataan barang. Awalnya saya pikir pekerjaannya sederhana. Paling hanya mencocokkan data yang sudah ada dengan barang yang ada di ruangan.

Ternyata setelah mulai dikerjakan, kondisinya tidak sesederhana itu.

Data utama memang sudah ada. Tetapi ketika dibandingkan dengan kondisi nyata di ruangan, ternyata banyak yang belum sesuai. Ada barang yang di data tercatat berada di lokasi tertentu, tetapi fisiknya ada di ruangan lain. Ada barang yang labelnya masih ada, tetapi ketika dicari di database utama tidak langsung ditemukan. Ada juga barang yang secara fisik ada, tetapi informasinya belum cocok dengan data utama.

Merapikan Data Inventaris Kantor dengan Google Spreadsheet

Dari situ saya mulai berpikir, kalau langsung ingin merapikan semuanya sekaligus, sepertinya akan berat. Jadi saya coba mulai dari langkah yang paling sederhana dulu, yaitu melakukan pendataan per ruangan.

Alurnya saya buat pelan-pelan:

  1. Cek barang langsung ke tiap ruangan.
  2. Catat manual dulu di tabel/form pendataan.
  3. Masukkan hasil catatan ke Google Spreadsheet.
  4. Cocokkan dengan database utama.
  5. Tandai data yang sudah diproses.
  6. Catat juga data yang tidak ditemukan atau perlu dicek ulang.

Jadi tulisan ini saya buat sebagai catatan belajar saya sendiri. Supaya kalau nanti lupa, saya bisa buka lagi.

Gambaran Alur Kerja

Sebelum masuk ke Google Spreadsheet, proses awal yang saya lakukan sebenarnya masih manual.

Saya mengecek barang langsung ke tiap ruangan, lalu mencatatnya di tabel/form pendataan sederhana. Jadi sumber input awalnya bukan langsung dari database utama, tetapi dari hasil pengecekan fisik di ruangan.

Misalnya saya masuk ke satu ruangan, lalu mulai mencatat barang-barang yang ada di sana. Saya catat nomor atau identitas item yang masih terbaca, nama item jika perlu, kondisi item, dan keterangan lain yang dianggap penting.

Setelah hasil catatan dari ruangan terkumpul, barulah data tersebut saya masukkan ke Google Spreadsheet.

Tujuannya bukan hanya mengetik ulang data, tetapi untuk membandingkan antara:

  • item yang benar-benar ada di ruangan,
  • item yang tercatat di database utama,
  • dan apakah lokasi item di data sudah sesuai dengan kondisi nyata.

Dengan cara ini, saya tidak hanya bergantung pada data lama. Database utama tetap dipakai sebagai pembanding, tetapi kondisi nyata di ruangan tetap menjadi dasar pengecekan awal.

Penomoran Ruangan

Sebelum membuat sheet per ruangan, saya sudah lebih dulu memberi nomor untuk tiap ruangan.

Tujuannya sederhana, agar proses pendataan lebih mudah dilacak.

Misalnya:

  • Ruang nomor 1 saya beri nama 1RuangRapat
  • Ruang nomor 38 saya beri nama 38RuangServer

Jadi angka 1, 2, 38, dan seterusnya itu bukan berarti urutan saya menginput data di spreadsheet. Angka itu adalah nomor ruangan yang memang sudah saya buat sebelumnya untuk memudahkan pendataan.

Dalam praktiknya, input data tidak harus dimulai dari ruangan nomor 1.

Misalnya ruangan nomor 38 lebih dulu selesai dicek secara manual, maka saya bisa input dulu ke sheet 38RuangServer. Kalau ruangan nomor 1 selesai belakangan, baru nanti saya input ke sheet 1RuangRapat.

Jadi urutan input mengikuti ruangan mana yang sudah selesai didata di lapangan, bukan harus urut dari nomor ruangan terkecil.

Ini penting supaya pekerjaan lebih fleksibel. Karena dalam proses cek fisik, kadang ruangan tertentu lebih mudah diakses, sedangkan ruangan lain harus menunggu waktu kosong, menunggu kunci, atau menunggu penanggung jawab ruangannya.

Struktur Sheet yang Saya Buat

Di Google Spreadsheet, saya membuat beberapa sheet.

Kurang lebih strukturnya seperti ini:

  1. Master
  2. Daftar_Ruangan
  3. Cek_Fisik
  4. Sheet per ruangan, misalnya:
    • 1RuangRapat
    • 38RuangServer

Nama sheet saya buat tanpa spasi agar lebih mudah dipakai di rumus. Misalnya saya pakai 1RuangRapat, bukan 1 Ruang Rapat.

Sheet Master

Sheet Master berisi database utama item.

Header-nya bisa disesuaikan dengan kebutuhan. Saya buat dengan istilah umum saja, misalnya:

  • No
  • Kode Unit
  • Nama Unit
  • Kode Item
  • ID Item
  • Nama Item
  • Status
  • Brand
  • Model
  • Kondisi
  • Lokasi
  • Keterangan

Yang paling penting untuk proses awal ini adalah kolom-kolom berikut:

Kolom Isi
ANo
FID Item
GNama Item
IBrand
KKondisi

Kolom No menjadi kunci utama. Jadi ketika saya mengisi nomor di sheet ruangan, data lain seperti nama item, ID item, brand, dan kondisi bisa otomatis ditarik dari sheet Master.

Sheet Daftar_Ruangan

Sheet Daftar_Ruangan saya gunakan untuk menyimpan daftar kode dan nama ruangan.

Formatnya sederhana:

Kode Ruangan Nama Ruangan
R001Ruang Rapat
R002Ruang Server
R003Ruang Administrasi

Misalnya:

  • Kolom A = Kode Ruangan
  • Kolom B = Nama Ruangan

Data dimulai dari baris 2, karena baris 1 dipakai untuk header.

Contoh rentang datanya:

A2:B40

Sheet ini nanti dipakai sebagai sumber dropdown di sheet Cek_Fisik.

Sheet Cek_Fisik

Sheet Cek_Fisik saya gunakan untuk menampung hasil pengecekan fisik barang di ruangan.

Jadi sebelum data dimasukkan ke sheet ini, saya terlebih dahulu mengecek barang secara langsung di tiap ruangan dan mencatatnya secara manual di tabel/form pendataan.

Setelah itu, hasil catatan tersebut baru saya masukkan ke Google Spreadsheet.

Di sheet Cek_Fisik, saya ingin ketika memilih kode ruangan, maka nama ruangannya otomatis muncul. Ini supaya proses input lebih rapi dan saya tidak perlu mengetik nama ruangan berulang-ulang.

Contohnya seperti ini:

Kode Ruangan Nama Ruangan
pilih dari dropdownotomatis muncul

Dengan cara ini, saya bisa lebih fokus mencatat hasil pengecekan fisik, lalu setelah itu membandingkannya dengan data yang ada di sheet Master.

Bisa dibilang, sheet Cek_Fisik ini menjadi jembatan antara kondisi nyata di ruangan dengan database utama yang sudah ada.

Langkah pertama yang saya buat adalah dropdown untuk kode ruangan.

Tujuannya supaya kode ruangan tidak diketik manual. Kalau diketik manual, kadang ada risiko salah ketik, kelebihan spasi, atau formatnya tidak sama.

Langkahnya:

  1. Buka sheet Cek_Fisik.
  2. Blok kolom yang ingin diberi dropdown, misalnya:
A2:A500
  1. Klik menu:
Data → Validasi data
  1. Pada bagian kriteria, pilih:
Dropdown dari rentang
  1. Masukkan rentang sumber data:
Daftar_Ruangan!A2:A40
  1. Klik selesai.

Setelah itu, kolom kode ruangan di sheet Cek_Fisik sudah memiliki dropdown. Jadi tinggal pilih kode ruangan dari daftar yang tersedia.

Membuat Nama Ruangan Otomatis Muncul

Setelah dropdown kode ruangan berhasil, langkah berikutnya adalah membuat nama ruangan otomatis muncul.

Misalnya di sheet Cek_Fisik:

Kolom Isi
AKode Ruangan
BNama Ruangan

Maka di cell B2, saya isi rumus berikut:

=IF(A2="";"";IFERROR(VLOOKUP(A2;Daftar_Ruangan!A:B;2;FALSE);"Tidak ditemukan"))

Setelah rumus ini dipasang, ketika saya memilih kode ruangan di kolom A, nama ruangan akan otomatis muncul di kolom B.

Penjelasan sederhananya:

  • A2 adalah kode ruangan yang dipilih.
  • Daftar_Ruangan!A:B adalah sumber data kode dan nama ruangan.
  • Angka 2 berarti mengambil data dari kolom kedua, yaitu nama ruangan.
  • FALSE berarti pencarian harus cocok persis.
  • IFERROR dipakai supaya kalau data tidak ditemukan, tidak langsung muncul error.

Catatan Penting tentang Koma, Titik Koma, dan Setting Wilayah

Ini salah satu hal kecil yang sempat membuat saya bingung.

Awalnya saya mencoba memasukkan rumus seperti ini:

=IF(A2="","",IFERROR(VLOOKUP(A2,Daftar_Ruangan!A:B,2,FALSE),"Tidak ditemukan"))

Ternyata muncul error.

Setelah dicek, masalahnya bukan pada logika rumusnya, tetapi pada setting wilayah atau locale Google Spreadsheet yang saya gunakan.

Kalau Google Spreadsheet memakai setting Indonesia, biasanya pemisah antarbagian rumus menggunakan titik koma ;, bukan koma ,.

Jadi untuk setting Indonesia, rumusnya menjadi seperti ini:

=IF(A2="";"";IFERROR(VLOOKUP(A2;Daftar_Ruangan!A:B;2;FALSE);"Tidak ditemukan"))

Perbedaannya kecil, tapi cukup penting.

Versi setting Inggris/US biasanya memakai koma:

=VLOOKUP(B2,Master!A:G,7,FALSE)

Sedangkan versi setting Indonesia memakai titik koma:

=VLOOKUP(B2;Master!A:G;7;FALSE)

Jadi kalau rumus terasa sudah benar tetapi tetap error, coba periksa dulu tanda pemisahnya.

Setting SpreadsheetPemisah Rumus
Indonesiatitik koma ;
Inggris/USkoma ,

Hal yang sama juga berlaku untuk rumus lain seperti IF, IFERROR, VLOOKUP, ARRAYFORMULA, COUNTIF, dan OR.

Contoh rumus format bersyarat dengan setting Indonesia:

=COUNTIF(INDIRECT("'1RuangRapat'!B:B");$A2)>0

Kalau memakai setting Inggris/US, biasanya menjadi:

=COUNTIF(INDIRECT("'1RuangRapat'!B:B"),$A2)>0

Bagi saya, ini pelajaran kecil tapi penting. Kadang kita merasa rumusnya salah, padahal yang salah hanya tanda pemisahnya.

Membuat Nama Ruangan Otomatis untuk Semua Baris

Kalau tidak ingin menarik rumus ke bawah satu per satu, bisa memakai ARRAYFORMULA.

Misalnya di cell B2 sheet Cek_Fisik:

=ARRAYFORMULA(IF(A2:A="";"";IFERROR(VLOOKUP(A2:A;Daftar_Ruangan!A:B;2;FALSE);"Tidak ditemukan")))

Dengan rumus ini, cukup pasang sekali di baris 2. Nanti baris-baris di bawahnya akan otomatis mengikuti.

Tapi ada catatan penting.

Kalau memakai ARRAYFORMULA, maka kolom hasilnya tidak bisa diisi manual di tengah-tengah. Karena seluruh kolom sudah dikendalikan oleh rumus dari baris atas.

Ini nanti menjadi penting ketika ada data yang tidak ditemukan di database utama dan perlu diisi manual.

Membuat Sheet Per Ruangan

Setelah sheet Cek_Fisik mulai rapi, saya lanjut membuat sheet per ruangan.

Contohnya saya membuat sheet:

1RuangRapat

dan juga:

38RuangServer

Sekali lagi, angka di depan nama sheet adalah nomor ruangan yang sudah saya tentukan sebelumnya. Jadi 1RuangRapat artinya ruangan nomor 1, sedangkan 38RuangServer artinya ruangan nomor 38.

Header di sheet ruangan kurang lebih seperti ini:

NoNo Urut Data UtamaNama ItemID ItemBrandKondisi

Kolom pentingnya:

KolomIsi
BNo Urut Data Utama
CNama Item
DID Item
EBrand
FKondisi

Konsepnya begini.

Saya cukup mengisi kolom No Urut Data Utama. Nomor tersebut sama dengan kolom No yang ada di sheet Master.

Setelah nomor diisi, maka data lain akan otomatis muncul dari sheet Master.

Dalam proses input, saya tidak harus mengisi sheet ruangan secara berurutan dari nomor 1. Kalau hasil cek fisik ruangan nomor 38 sudah selesai duluan, saya bisa langsung mengisi sheet 38RuangServer. Nanti kalau ruangan nomor 1 baru selesai dicek, baru saya isi sheet 1RuangRapat.

Jadi spreadsheet ini mengikuti alur kerja nyata di lapangan, bukan memaksa harus urut dari awal.

Menarik Data dari Master

Menarik Nama Item dari Master

Di sheet 1RuangRapat, kolom B adalah No Urut Data Utama. Kolom C adalah Nama Item.

Karena di sheet Master, kolom A adalah No dan kolom G adalah Nama Item, maka di cell C2, saya isi rumus:

=IF(B2="";"";IFERROR(VLOOKUP(B2;Master!A:G;7;FALSE);""))

Menarik ID Item dari Master

Untuk kolom D atau ID Item, rumus di cell D2:

=IF(B2="";"";IFERROR(VLOOKUP(B2;Master!A:F;6;FALSE);""))

Menarik Brand dari Master

Untuk kolom E atau Brand, rumus di cell E2:

=IF(B2="";"";IFERROR(VLOOKUP(B2;Master!A:I;9;FALSE);""))

Menarik Kondisi dari Master

Untuk kolom F atau Kondisi, rumus di cell F2:

=IF(B2="";"";IFERROR(VLOOKUP(B2;Master!A:K;11;FALSE);""))

Setelah semua rumus dipasang di baris 2, rumus bisa ditarik ke bawah sesuai kebutuhan.

Versi Otomatis dengan ARRAYFORMULA

Awalnya saya mencoba memakai ARRAYFORMULA, supaya tidak perlu menarik rumus ke bawah.

Untuk Nama Item di C2:

=ARRAYFORMULA(IF(B2:B="";"";IFERROR(VLOOKUP(B2:B;Master!A:G;7;FALSE);"Tidak ditemukan")))

Untuk ID Item di D2:

=ARRAYFORMULA(IF(B2:B="";"";IFERROR(VLOOKUP(B2:B;Master!A:F;6;FALSE);"Tidak ditemukan")))

Untuk Brand di E2:

=ARRAYFORMULA(IF(B2:B="";"";IFERROR(VLOOKUP(B2:B;Master!A:I;9;FALSE);"Tidak ditemukan")))

Untuk Kondisi di F2:

=ARRAYFORMULA(IF(B2:B="";"";IFERROR(VLOOKUP(B2:B;Master!A:K;11;FALSE);"Tidak ditemukan")))

Dengan cara ini, semua baris otomatis ikut terisi ketika kolom No Urut Data Utama diisi. Tapi setelah dipakai, ternyata ada kasus khusus.

Ketika Data Tidak Ada di Master dan Perlu Diisi Manual

Saat mulai pendataan, saya menemukan kasus seperti ini.

Identitas item ada secara fisik, tetapi ketika dicari di database utama, datanya tidak ditemukan. Bisa jadi karena data belum diperbarui, lokasinya belum sesuai, atau memang perlu dicek ulang.

Masalahnya, kalau kolom memakai ARRAYFORMULA, saya tidak bisa mengetik manual di kolom tersebut. Ketika dipaksa diisi manual, muncul error karena cell itu bagian dari hasil array.

Dari sini saya belajar, untuk pekerjaan pendataan inventaris yang datanya belum sepenuhnya rapi, lebih fleksibel menggunakan rumus biasa per baris, bukan ARRAYFORMULA.

Jadi solusinya:

  1. Hapus rumus ARRAYFORMULA.
  2. Pakai rumus biasa di baris 2.
  3. Tarik rumus ke bawah.
  4. Jika ada data yang perlu diisi manual, hapus rumus di cell tersebut.
  5. Isi manual sesuai hasil pengecekan.

Rumus yang saya pakai akhirnya seperti ini:

C2 - Nama Item

=IF(B2="";"";IFERROR(VLOOKUP(B2;Master!A:G;7;FALSE);""))

D2 - ID Item

=IF(B2="";"";IFERROR(VLOOKUP(B2;Master!A:F;6;FALSE);""))

E2 - Brand

=IF(B2="";"";IFERROR(VLOOKUP(B2;Master!A:I;9;FALSE);""))

F2 - Kondisi

=IF(B2="";"";IFERROR(VLOOKUP(B2;Master!A:K;11;FALSE);""))

Dengan cara ini, kalau ada data yang tidak ditemukan di Master, saya masih bisa mengisi manual di baris tertentu.

Untuk kondisi data inventaris yang masih perlu dirapikan, cara ini terasa lebih aman.

Menandai Data di Master Jika Sudah Diproses

Setelah data dari Master ditarik ke sheet ruangan, saya ingin di sheet Master ada tanda otomatis.

Misalnya nomor 280 di Master adalah item tertentu. Kalau nomor 280 sudah dimasukkan ke sheet 1RuangRapat, maka cell nomor 280 di sheet Master otomatis berubah warna.

Tujuannya agar saya tahu item mana yang sudah diproses dan item mana yang belum.

Caranya menggunakan Format Bersyarat atau Conditional Formatting.

Format Bersyarat untuk Satu Sheet Ruangan

Misalnya sheet Master kolom A adalah No, dan sheet 1RuangRapat kolom B adalah No Urut Data Utama. Saya ingin jika Master!A2 ditemukan di 1RuangRapat!B:B, maka cell tersebut berubah warna.

Awalnya saya mencoba rumus seperti ini:

=COUNTIF('1RuangRapat'!B:B;$A2)>0

Tapi ternyata Google Sheets tidak mengizinkan format bersyarat langsung mengambil referensi dari sheet lain.

Solusinya adalah memakai INDIRECT.

Rumus yang berhasil:

=COUNTIF(INDIRECT("'1RuangRapat'!B:B");$A2)>0

Langkahnya:

  1. Buka sheet Master.
  2. Blok rentang data yang ingin diberi warna, misalnya:
A2:A620
  1. Klik menu:
Format → Format bersyarat
  1. Pada bagian aturan, pilih:
Formula khususnya adalah
  1. Masukkan rumus:
=COUNTIF(INDIRECT("'1RuangRapat'!B:B");$A2)>0
  1. Pilih warna, misalnya hijau.
  2. Klik selesai.

Sekarang, kalau nomor di Master sudah ada di sheet 1RuangRapat, maka cell nomor tersebut akan berubah warna.

Kenapa Saya Pakai A2:A620?

Awalnya saya ingin memakai rentang seperti ini:

A2:A

atau untuk satu baris penuh:

A2:CM

Tapi di bagian format bersyarat, kadang Google Sheets menolak dan muncul pesan:

Masukkan rentang yang valid

Saya juga sempat mencoba:

A2:CM9999

Tetapi tetap tidak bisa.

Kemungkinan karena kolom terakhir yang tersedia atau terbaca di sheet belum sampai CM, atau format rentangnya belum diterima di bagian format bersyarat.

Karena data saya saat ini hanya sekitar 600-an baris, akhirnya saya pakai rentang yang lebih aman:

A2:A620

Artinya, yang berubah warna hanya kolom No, dari baris 2 sampai 620.

Untuk kebutuhan awal, ini sudah cukup. Yang penting saya bisa melihat nomor mana saja yang sudah diproses.

Menambahkan Sheet Ruangan Lain

Setelah berhasil untuk 1RuangRapat, saya ingin menambahkan sheet lain, misalnya:

38RuangServer

Kalau semua sheet ingin diberi warna yang sama, rumusnya bisa digabung memakai OR.

Contohnya:

=OR(COUNTIF(INDIRECT("'1RuangRapat'!B:B");$A2)>0;COUNTIF(INDIRECT("'38RuangServer'!B:B");$A2)>0)

Artinya:

Jika nomor di Master!A2 ditemukan di sheet 1RuangRapat atau 38RuangServer, maka cell tersebut akan berubah warna.

Rumus ini cocok kalau semua ruangan ingin diberi warna yang sama.

Memberi Warna Berbeda untuk Tiap Sheet Ruangan

Setelah dicoba, saya merasa akan lebih enak kalau setiap sheet ruangan punya warna berbeda.

Misalnya:

  • 1RuangRapat = hijau
  • 38RuangServer = kuning atau warna lain

Kalau ingin warna berbeda, jangan memakai rumus OR.

Caranya adalah membuat beberapa aturan Format Bersyarat.

Aturan 1: 1RuangRapat Warna Hijau

Di sheet Master, buat aturan format bersyarat.

Terapkan ke rentang:

A2:A620

Formula khususnya:

=COUNTIF(INDIRECT("'1RuangRapat'!B:B");$A2)>0

Pilih warna hijau, lalu klik selesai.

Aturan 2: 38RuangServer Warna Lain

Tambahkan aturan baru.

Terapkan ke rentang:

A2:A620

Formula khususnya:

=COUNTIF(INDIRECT("'38RuangServer'!B:B");$A2)>0

Pilih warna lain, misalnya kuning, lalu klik selesai.

Dengan cara ini, nomor item di Master akan berubah warna sesuai sheet ruangan tempat nomor tersebut dipakai.

Kalau nanti ada sheet lain, tinggal tambah aturan baru lagi.

Misalnya:

=COUNTIF(INDIRECT("'NamaSheetLain'!B:B");$A2)>0

Lalu pilih warna lain sesuai kebutuhan.

Catatan tentang Nama Sheet

Saya memakai nama sheet tanpa spasi, misalnya:

1RuangRapat
38RuangServer

Menurut saya ini lebih aman dan mudah dipakai di rumus.

Tapi walaupun tanpa spasi, saya tetap menulis nama sheet di dalam tanda petik seperti ini:

INDIRECT("'1RuangRapat'!B:B")

Ini untuk menghindari error, terutama karena nama sheet diawali angka.

Jika Satu Nomor Muncul di Dua Sheet

Idealnya, satu nomor item hanya masuk ke satu sheet ruangan.

Tapi kalau ternyata satu nomor masuk di dua sheet sekaligus, warna yang tampil bisa mengikuti aturan format bersyarat yang posisinya lebih atas atau lebih dominan di daftar aturan.

Jadi untuk menjaga data tetap rapi, saya harus hati-hati agar satu nomor tidak tercatat di dua ruangan berbeda, kecuali memang sedang dalam proses pengecekan.

Alur Kerja yang Saya Pakai

Setelah beberapa kali coba dan error, alur kerja yang saya pakai kira-kira seperti ini:

  1. Siapkan sheet Master dari database item.
  2. Siapkan sheet Daftar_Ruangan.
  3. Beri nomor dan nama untuk tiap ruangan agar mudah dilacak.
  4. Lakukan pengecekan fisik ke tiap ruangan.
  5. Catat hasil pengecekan secara manual di tabel/form pendataan.
  6. Masukkan hasil catatan tersebut ke sheet Cek_Fisik atau sheet ruangan.
  7. Input data tidak harus urut dari ruangan nomor 1, tetapi mengikuti ruangan mana yang sudah selesai dicek lebih dulu.
  8. Buat dropdown kode ruangan di sheet Cek_Fisik.
  9. Buat nama ruangan otomatis muncul berdasarkan kode ruangan.
  10. Buat sheet per ruangan, misalnya 1RuangRapat dan 38RuangServer.
  11. Isi No Urut Data Utama di sheet ruangan.
  12. Nama item, ID item, brand, dan kondisi otomatis ditarik dari sheet Master.
  13. Jika data tidak ditemukan di Master, isi manual di cell tertentu.
  14. Di sheet Master, buat format bersyarat untuk menandai nomor yang sudah diproses.
  15. Jika ingin beda warna tiap ruangan, buat aturan format bersyarat masing-masing sheet.

Dengan alur ini, pekerjaan menjadi lebih masuk akal. Saya tidak langsung memaksa data di spreadsheet harus benar semua. Saya mulai dari kondisi nyata dulu, lalu membandingkannya dengan database utama.

Kesimpulan Sementara

Dari proses ini saya belajar bahwa merapikan data inventaris tidak cukup hanya dengan melihat data di sistem. Tetap harus dicocokkan dengan kondisi nyata di ruangan.

Google Spreadsheet cukup membantu untuk tahap awal, terutama untuk:

  • membuat dropdown kode ruangan,
  • menarik nama ruangan otomatis,
  • mengambil data item dari database utama,
  • menandai data yang sudah diproses,
  • dan memudahkan pengecekan per ruangan.

Penomoran ruangan juga cukup membantu. Jadi meskipun input data tidak harus urut dari ruangan nomor 1, setiap data tetap bisa dilacak berdasarkan nomor dan nama ruangan yang sudah dibuat sebelumnya.

Untuk data yang sudah rapi, ARRAYFORMULA memang enak karena otomatis. Tapi untuk data inventaris yang masih banyak kemungkinan tidak cocok antara database dan kondisi fisik, rumus biasa per baris lebih fleksibel karena masih bisa diisi manual jika diperlukan.

Ini baru tahap awal. Setidaknya saya mulai punya alur yang lebih jelas untuk merapikan data inventaris kantor pelan-pelan, dimulai dari pendataan per ruangan, lalu dicocokkan dengan database utama.

Semoga catatan ini bermanfaat, terutama buat saya sendiri kalau nanti lupa langkah-langkahnya.

www.CodeNirvana.in

Online Sejak 2013 Dwi Yanto | Blogger Templates | Designed By Code Nirvana