Blog

  • Aplikasi Order Percetakan: Spek, Proof Desain, dan Deadline

    Aplikasi Order Percetakan: Spek, Proof Desain, dan Deadline

    Aplikasi order percetakan pada dasarnya menjaga tiga hal. Pertama, spek pesanan tidak salah catat. Kedua, persetujuan desain terekam jelas, jadi ada bukti versi mana yang disetujui pelanggan sebelum naik cetak. Ketiga, deadline yang dijanjikan ke pelanggan sesuai dengan antrian mesin yang sebenarnya. Tiga hal ini yang paling sering bocor kalau semua order dicatat lewat chat WA. Ukuran banner tertukar. Pelanggan bilang sudah setuju desain, lalu komplain hasil cetak beda. Deadline dijanjikan asal cepat, padahal antrian mesin sudah penuh sampai lusa.

    Bukan berarti semua percetakan wajib pakai sistem hari ini juga. Kalau order harian masih sedikit dan semua dikerjakan satu orang, buku order plus disiplin biasanya masih cukup. Masalah muncul begitu ada admin yang terima order, desainer yang pegang file, dan operator yang jalankan mesin, lalu ketiganya bertukar informasi lewat chat. Di titik itu kesalahan bukan lagi soal teliti atau tidak. Formatnya memang tidak dirancang untuk kerja tim. Artikel ini membahas apa saja yang dijaga aplikasi order percetakan, kapan Anda benar-benar membutuhkannya, dan pilihan antara aplikasi jadi atau custom.

    Spek pesanan: satu detail salah, biayanya cetak ulang

    Order percetakan itu padat detail. Satu pesanan banner saja membawa banyak variabel sekaligus. Ukuran, bahan flexi berapa gram, jumlah, mata ayam di sisi mana, sampai file desain dari pelanggan yang kadang dikirim dalam format seadanya. Pindah ke brosur, variabelnya ganti. Jenis kertas, gramasi, satu muka atau dua muka, laminasi doff atau glossy, dilipat atau tidak. Belum lagi buku yasin, undangan, atau nota yang bawa urusan jilid dan penomoran.

    Yang bikin bisnis ini keras adalah harga dari satu kesalahan. Salah baca gramasi, salah potong, atau salah ambil file berarti cetak ulang. Bahan terbuang, waktu mesin terbuang, dan margin order itu bisa langsung habis. Padahal sumber salahnya sering sepele. Spek dikirim pelanggan lewat chat panjang, admin menyalin ke buku, operator membaca tulisan tangan yang beda tafsir. Tiga kali pindah tangan, tiga kali peluang salah.

    Di aplikasi order, spek diisi lewat form dengan kolom yang jelas. Ukuran, bahan, gramasi, jumlah, dan finishing masing-masing punya tempat sendiri. Kolom penting dibuat wajib, jadi order tidak bisa masuk antrian kalau speknya bolong. Surat perintah kerja untuk operator dicetak dari data yang sama persis dengan yang dilihat admin. Tidak ada lagi tafsir ulang dari tulisan tangan atau scroll chat mencari pesan dua hari lalu.

    Proof desain: approval tertulis sebelum naik cetak

    Hampir semua percetakan pernah kena kasus ini. Desain direvisi bolak-balik di WA. File menumpuk dengan nama final, final2, sampai fix-final-banget. Operator mengambil file yang ternyata bukan versi terakhir. Atau lebih repot lagi, pelanggan komplain hasil cetak beda dari yang dia mau, sementara jejak persetujuannya tenggelam di antara ratusan chat.

    Sistem order yang baik merapikan alur ini. Setiap versi proof tercatat sebagai versi terpisah, lengkap dengan tanggal dan catatan revisinya. Pelanggan menyetujui satu versi tertentu, dan persetujuan itu terekam dengan waktu yang jelas. Order baru boleh naik cetak setelah ada approval di versi final. Operator pun hanya bisa mengambil file dari versi yang disetujui, bukan menebak dari folder yang penuh file mirip.

    Manfaatnya terasa justru saat ada komplain. Anda tinggal buka riwayat order dan tunjukkan versi yang disetujui beserta waktunya. Tujuannya bukan untuk menang debat dengan pelanggan, tapi supaya dua pihak melihat data yang sama dan urusannya cepat selesai. Bagi tim internal, aturan wajib approval sebelum cetak juga jadi rem. Tidak ada lagi naik cetak karena merasa pelanggan sudah oke, padahal belum ada persetujuan hitam di atas putih.

    Antrian mesin, status order, dan janji deadline yang masuk akal

    Sumber lembur di percetakan sering bukan volume order, tapi janji yang tidak dihitung. Pelanggan minta besok jadi, admin langsung mengiyakan karena tidak tahu antrian mesin sedang seperti apa. Akhirnya order lain digeser, operator lembur, dan tetap ada pelanggan yang kecewa karena ordernya molor.

    Aplikasi order menampilkan antrian per mesin atau per jalur kerja. Mesin outdoor, mesin indoor, digital A3, dan meja finishing masing-masing punya daftar antriannya sendiri. Saat order baru masuk, admin bisa lihat beban hari itu dan memberi estimasi selesai dari antrian nyata, bukan dari perasaan. Kalau pelanggan tetap minta cepat, order rush bisa ditandai, dan semua orang tahu order mana yang digeser sebagai gantinya. Keputusannya jadi sadar, bukan kejutan di sore hari.

    Dari sisi pelanggan, yang paling terasa adalah status order yang bisa dicek. Alurnya mengikuti tahapan kerja percetakan. Antri, proses desain, tunggu approval, naik cetak, finishing, siap ambil. Pelanggan tidak perlu menelepon dua kali sehari untuk tanya sudah jadi atau belum, dan admin tidak perlu berhenti kerja untuk menjawab pertanyaan yang sama berulang-ulang. Untuk pemilik, papan status ini juga jadi cara cepat melihat order mana yang macet di tahap tunggu approval terlalu lama.

    DP, arsip file, dan laporan yang selama ini tidak sempat dibuat

    Urusan uang di percetakan punya polanya sendiri. Kebanyakan order jalan dengan DP dulu, pelunasan saat barang diambil. Kalau catatannya tercecer, selalu ada saja order yang diambil tanpa ketahuan kurang bayarnya. Di sistem, DP dan pelunasan menempel di masing-masing order. Saat barang mau diserahkan, sisa tagihan langsung kelihatan.

    Soal harga, percetakan memang tidak seperti toko biasa. Harga tergantung kombinasi spek, dan kombinasinya nyaris tidak terbatas. Di tahap awal, cukup harga diinput manual per order oleh admin yang paham hitungannya. Kalkulator harga otomatis yang menghitung dari ukuran, bahan, jumlah, dan finishing itu fitur lanjutan. Berguna, tapi bukan syarat untuk mulai. Jangan sampai proyek sistem tertunda setahun hanya karena menunggu rumus harga sempurna.

    Satu hal lagi yang sering diremehkan, arsip file pelanggan. Setiap order menyimpan file desain finalnya. Saat pelanggan yang sama mau cetak ulang kop surat atau nota tiga bulan kemudian, admin tinggal panggil file lama dari riwayat ordernya. Pesanan selesai lebih cepat, pelanggan merasa dilayani seperti langganan, dan peluang repeat order naik. Pola menjaga pelanggan lama seperti ini mirip dengan yang pernah kami bahas di artikel cara follow up pelanggan yang tanya harga lalu hilang. Data yang rapi membuat follow up jadi mungkin.

    Terakhir, laporan untuk pemilik. Dari data order yang sama, sistem bisa menjawab pertanyaan yang selama ini hanya dijawab dengan kira-kira. Produk apa yang paling menyumbang omzet, banner atau undangan. Siapa sepuluh pelanggan terbesar yang layak dijaga baik-baik. Seberapa sering order rush masuk dan bikin lembur, dan apakah harga rush yang dipasang sudah sepadan. Jawaban seperti ini yang membedakan keputusan bisnis dari sekadar firasat.

    Kapan buku order masih cukup, dan pilihan sistemnya

    Supaya adil, tidak semua percetakan butuh aplikasi sekarang. Kalau order masuk hitungan jari per hari, Anda sendiri yang terima order, pegang desain, dan jalankan mesin, buku order masih cukup. Informasinya tidak pindah tangan, jadi risiko salah tafsirnya kecil. Uang untuk sistem lebih baik dipakai menambah alat atau stok bahan dulu.

    Tandanya Anda mulai butuh sistem biasanya jelas. Cetak ulang karena salah spek terjadi lebih dari sekali sebulan. Ada pelanggan komplain hasil beda dan Anda tidak bisa menunjukkan bukti persetujuan. Admin menghabiskan banyak waktu menjawab pertanyaan status order. Atau Anda sebagai pemilik tidak bisa jawab produk mana yang paling menguntungkan tanpa membuka tumpukan nota.

    Kalau sudah sampai di titik itu, ada dua jalan. Aplikasi jadi untuk percetakan ada di pasaran, umumnya berbentuk POS dengan modul order. Cocok kalau alur kerja Anda standar dan Anda bersedia menyesuaikan kebiasaan tim ke alur aplikasi. Sistem custom masuk akal kalau alur Anda punya kekhasan yang tidak tertampung, misalnya tahapan proof yang ketat, antrian banyak mesin, atau aturan harga dan DP yang khas usaha Anda. Gambaran cara kami membangun sistem aplikasi custom bisa dibaca di halaman layanan kami.

    Kalau Anda masih menimbang, tidak perlu langsung memutuskan. Ceritakan dulu alur order di percetakan Anda lewat halaman kontak Arrazy. Dari situ biasanya kelihatan apakah masalahnya cukup dijawab aplikasi jadi, atau memang butuh sistem yang mengikuti cara kerja Anda.

    Pertanyaan yang sering muncul

    Berapa biaya membuat aplikasi order percetakan custom?

    Tergantung cakupan fitur. Versi awal yang fokus di pencatatan order, spek, status, dan pembayaran jauh lebih terjangkau daripada sistem lengkap dengan kalkulator harga otomatis dan portal pelanggan. Cara paling aman adalah mulai dari fitur yang menutup kebocoran terbesar dulu, lalu menambah bertahap.

    Apakah pelanggan harus install aplikasi untuk cek status order?

    Tidak harus. Pola yang umum, pelanggan cukup membuka link status lewat browser HP tanpa install apa pun. Approval desain juga bisa lewat link yang sama. Yang memakai aplikasi secara penuh cukup tim internal, yaitu admin, desainer, dan operator.

    Berapa lama sampai sistemnya bisa dipakai?

    Untuk versi awal dengan fitur inti, hitungannya biasanya beberapa minggu sampai dua bulanan, tergantung kompleksitas alur. Yang sering lebih menentukan justru kesiapan tim memindahkan kebiasaan dari chat ke sistem. Mulai dari fitur sedikit tapi dipakai disiplin lebih baik daripada fitur lengkap yang tidak tersentuh.

  • Belajar Database dari Nol #5: INSERT, Menambah Data ke Tabel

    Belajar Database dari Nol #5: INSERT, Menambah Data ke Tabel

    Cara paling dasar untuk insert data MySQL adalah perintah INSERT INTO nama_tabel (kolom1, kolom2) VALUES (nilai1, nilai2);. Satu perintah bisa memasukkan satu baris, bisa juga puluhan baris sekaligus dengan memisahkan tiap baris pakai koma. Di MySQL 8.4, perintah ini juga yang paling sering dipakai di dunia kerja, baik lewat terminal langsung maupun lewat kode aplikasi.

    Artikel ini bagian kelima dari seri Belajar Database dari Nol. Di bagian ini kita tidak cuma belajar sintaks. Kita sekalian menyiapkan dataset studi kasus toko online berisi tabel produk, pelanggan, dan pesanan. Dataset ini akan dipakai terus sampai akhir seri, jadi kerjakan bagian ini sampai selesai supaya bagian berikutnya tinggal jalan.

    Prasyarat: MySQL 8.4 dan Database Kosong

    Kamu butuh MySQL Server 8.4 yang sudah jalan dan bisa login lewat terminal. Kalau belum, mundur dulu ke bagian pertama seri untuk instalasi. Kamu juga sebaiknya sudah paham kenapa kolom harga pakai DECIMAL dan bukan FLOAT. Itu dibahas tuntas di Belajar Database dari Nol #4: Tipe Data MySQL yang Tepat.

    Supaya semua pembaca mulai dari titik yang sama, kita buat database baru khusus studi kasus. Login dulu ke MySQL, lalu jalankan ini:

    CREATE DATABASE IF NOT EXISTS toko_online;
    USE toko_online;

    Sekarang buat tiga tabel utamanya. Relasi antar tabel dengan foreign key baru kita pasang di bagian 11, jadi untuk sekarang kolom pelanggan_id dan produk_id di tabel pesanan masih berupa angka biasa:

    CREATE TABLE produk (
      id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
      nama VARCHAR(100) NOT NULL,
      kategori VARCHAR(50) NOT NULL,
      harga DECIMAL(12,2) NOT NULL,
      stok INT UNSIGNED NOT NULL DEFAULT 0
    );
    
    CREATE TABLE pelanggan (
      id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
      nama VARCHAR(100) NOT NULL,
      email VARCHAR(100) NOT NULL UNIQUE,
      kota VARCHAR(50),
      terdaftar_pada DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    
    CREATE TABLE pesanan (
      id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
      pelanggan_id INT UNSIGNED NOT NULL,
      produk_id INT UNSIGNED NOT NULL,
      jumlah INT UNSIGNED NOT NULL DEFAULT 1,
      total DECIMAL(12,2) NOT NULL,
      status VARCHAR(20) NOT NULL DEFAULT 'pending',
      tanggal_pesan DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
    );

    Kalau ketiga perintah sukses, tiap CREATE TABLE mengembalikan Query OK, 0 rows affected. Cek dengan SHOW TABLES; dan pastikan muncul tiga tabel: pelanggan, pesanan, produk.

    INSERT INTO Satu Baris: Bentuk Paling Dasar

    Kita mulai dari satu baris. Masukkan satu produk ke tabel produk:

    INSERT INTO produk (nama, kategori, harga, stok)
    VALUES ('Kaos Polos Hitam', 'Pakaian', 55000, 120);

    Output yang diharapkan:

    Query OK, 1 row affected (0.01 sec)

    Perhatikan strukturnya. Setelah nama tabel ada daftar kolom dalam kurung, lalu VALUES berisi nilai dengan urutan yang sama persis. Nilai teks diapit tanda kutip satu, angka ditulis polos tanpa kutip.

    Satu jebakan kecil soal tanda kutip. Kalau nilai teksnya sendiri mengandung tanda kutip satu, misalnya nama produk Kaos Sablon 'Limited', kamu harus menuliskannya dua kali berturut-turut: 'Kaos Sablon ''Limited'''. Alternatifnya pakai garis miring terbalik, 'Kaos Sablon \'Limited\''. Kalau lupa, MySQL menganggap string sudah selesai di kutip pertama dan sisanya dianggap sintaks rusak.

    Sebenarnya MySQL mengizinkan kamu melewatkan daftar kolom, seperti INSERT INTO produk VALUES (...);. Jangan dibiasakan. Tanpa daftar kolom, kamu wajib mengisi semua kolom sesuai urutan di tabel, termasuk kolom id. Begitu struktur tabel berubah, misalnya ada kolom baru, query lama langsung error. Menyebut kolom secara eksplisit membuat query tetap jalan dan lebih mudah dibaca orang lain.

    INSERT Multi Baris Sekaligus, Lebih Cepat dan Lebih Rapi

    Kalau mau memasukkan lima produk, kamu tidak perlu menulis lima perintah. Cukup satu INSERT dengan beberapa kelompok VALUES yang dipisah koma:

    INSERT INTO produk (nama, kategori, harga, stok) VALUES
    ('Kemeja Flanel Kotak', 'Pakaian', 145000, 45),
    ('Sepatu Lari Ringan', 'Sepatu', 320000, 30),
    ('Tas Ransel Laptop 15 inci', 'Tas', 210000, 25),
    ('Topi Baseball Navy', 'Aksesoris', 48000, 80),
    ('Jaket Hoodie Abu', 'Pakaian', 175000, 60);

    Output yang diharapkan:

    Query OK, 5 rows affected (0.01 sec)
    Records: 5  Duplicates: 0  Warnings: 0

    Selain lebih ringkas, cara ini jauh lebih cepat karena server hanya memproses satu perintah untuk banyak baris. Di proyek klien, tim Arrazy memakai pola multi-baris seperti ini untuk seeder, yaitu skrip pengisi data awal, baik lewat migration Laravel maupun program seeding di backend Go. Prinsipnya sama persis dengan yang kamu tulis manual di sini.

    Perilaku AUTO_INCREMENT, NULL, dan DEFAULT Saat Kolom Tidak Diisi

    Kamu mungkin sadar dari tadi kita tidak pernah mengisi kolom id. Itu bukan kelalaian. Ada tiga mekanisme yang bekerja diam-diam saat sebuah kolom tidak disebut di perintah INSERT:

    Mekanisme Kapan aktif Nilai yang diisi
    AUTO_INCREMENT Kolom bertanda AUTO_INCREMENT tidak diisi Angka berikutnya, mulai dari 1 lalu naik terus
    DEFAULT Kolom punya klausa DEFAULT Nilai default yang ditulis saat membuat tabel
    NULL Kolom boleh kosong dan tanpa default NULL, alias tidak ada nilai

    Coba buktikan dengan tabel pelanggan. Masukkan satu pelanggan tanpa menyebut id, kota, dan terdaftar_pada:

    INSERT INTO pelanggan (nama, email)
    VALUES ('Budi Santoso', 'budi@example.com');
    
    SELECT * FROM pelanggan;

    Hasilnya kira-kira begini:

    +----+--------------+------------------+------+---------------------+
    | id | nama         | email            | kota | terdaftar_pada      |
    +----+--------------+------------------+------+---------------------+
    |  1 | Budi Santoso | budi@example.com | NULL | 2026-07-27 09:15:42 |
    +----+--------------+------------------+------+---------------------+

    Tiga hal terjadi sekaligus. Kolom id terisi 1 dari AUTO_INCREMENT. Kolom kota jadi NULL karena boleh kosong dan tidak punya default. Kolom terdaftar_pada terisi waktu saat ini karena defaultnya CURRENT_TIMESTAMP.

    Kalau kamu butuh id yang baru saja dibuat, misalnya untuk dipakai di tabel lain, panggil SELECT LAST_INSERT_ID(); tepat setelah insert. Untuk memaksa kolom memakai nilai defaultnya secara eksplisit, tulis kata kunci DEFAULT di posisi nilainya, contohnya VALUES ('Sandal Jepit', 'Sepatu', 25000, DEFAULT) yang membuat stok terisi 0.

    Satu catatan penting soal NULL. NULL bukan nol dan bukan string kosong. NULL artinya nilainya memang tidak diketahui. Kolom yang ditandai NOT NULL menolak nilai ini, dan itu bagus untuk data yang wajib ada seperti nama produk atau harga.

    Mengisi Dataset Studi Kasus Toko Online

    Sekarang kita lengkapi dataset yang akan dipakai sepanjang seri. Tabel produk sudah berisi 6 baris. Tambahkan pelanggan dan pesanan berikut apa adanya, jangan diubah dulu, supaya hasil query kamu di bagian-bagian berikutnya sama dengan contoh di artikel:

    INSERT INTO pelanggan (nama, email, kota) VALUES
    ('Siti Rahayu', 'siti@example.com', 'Semarang'),
    ('Agus Wijaya', 'agus@example.com', 'Purwokerto'),
    ('Dewi Lestari', 'dewi@example.com', 'Banjarnegara'),
    ('Rudi Hartono', 'rudi@example.com', NULL);
    
    INSERT INTO pesanan (pelanggan_id, produk_id, jumlah, total, status) VALUES
    (1, 1, 2, 110000, 'selesai'),
    (1, 4, 1, 48000, 'selesai'),
    (2, 3, 1, 320000, 'dikirim'),
    (3, 6, 1, 175000, 'pending'),
    (4, 2, 2, 290000, 'selesai'),
    (5, 5, 3, 630000, 'dibatalkan');

    Perhatikan baris Rudi Hartono. Kita sengaja menulis NULL secara eksplisit di kolom kota. Ini sah karena kolom kota memang boleh kosong, dan nanti berguna saat belajar filter IS NULL. Di tabel pesanan, kolom tanggal_pesan kita biarkan terisi otomatis oleh default CURRENT_TIMESTAMP.

    Total dataset kamu sekarang: 6 produk, 5 pelanggan, dan 6 pesanan. Struktur toko online sederhana seperti ini juga jadi fondasi sistem yang lebih besar. Pola tabel produk, pelanggan, dan pesanan yang sama kami pakai saat membangun sistem aplikasi untuk klien, tentu dengan kolom dan relasi yang jauh lebih banyak.

    Cek Hasil dengan SELECT * Sebelum Lanjut

    Biasakan langsung memeriksa hasil setiap selesai memasukkan data. Cara tercepatnya adalah SELECT *, yang artinya tampilkan semua kolom:

    SELECT * FROM produk;
    SELECT * FROM pelanggan;
    SELECT * FROM pesanan;

    Untuk tabel produk, hasilnya harus 6 baris dengan id berurutan 1 sampai 6:

    +----+---------------------------+-----------+-----------+------+
    | id | nama                      | kategori  | harga     | stok |
    +----+---------------------------+-----------+-----------+------+
    |  1 | Kaos Polos Hitam          | Pakaian   |  55000.00 |  120 |
    |  2 | Kemeja Flanel Kotak       | Pakaian   | 145000.00 |   45 |
    |  3 | Sepatu Lari Ringan        | Sepatu    | 320000.00 |   30 |
    |  4 | Tas Ransel Laptop 15 inci | Tas       | 210000.00 |   25 |
    |  5 | Topi Baseball Navy        | Aksesoris |  48000.00 |   80 |
    |  6 | Jaket Hoodie Abu          | Pakaian   | 175000.00 |   60 |
    +----+---------------------------+-----------+-----------+------+
    6 rows in set (0.00 sec)

    Kalau jumlah barisnya tidak cocok, hapus isi tabel dengan TRUNCATE TABLE nama_tabel; lalu ulangi insert dari awal. TRUNCATE mengosongkan tabel sekaligus mereset hitungan AUTO_INCREMENT kembali ke 1, jadi id kamu kembali rapi. Untuk sekarang cukup SELECT * saja dulu. Menyaring baris tertentu dengan kondisi adalah materi bagian berikutnya.

    Kebiasaan cek cepat seperti ini terlihat sepele, tapi menyelamatkan banyak waktu. Salah ketik satu angka di kolom total jauh lebih murah ditemukan sekarang, saat datanya masih 6 baris, daripada nanti saat kamu bingung kenapa hasil agregasi di bagian 10 tidak masuk akal. Data yang salah sejak insert akan salah terus di semua query di atasnya.

    Troubleshooting: Error INSERT yang Paling Sering Muncul

    ERROR 1062: Duplicate entry for key PRIMARY

    ERROR 1062 (23000): Duplicate entry '1' for key 'produk.PRIMARY'

    Penyebab: kamu memasukkan nilai id yang sudah dipakai baris lain, biasanya karena mengisi kolom AUTO_INCREMENT secara manual. Primary key harus unik, jadi MySQL menolak. Error yang sama muncul di kolom UNIQUE, misalnya memasukkan email pelanggan yang sudah terdaftar. Solusi: jangan isi kolom id sama sekali, biarkan AUTO_INCREMENT bekerja. Untuk email duplikat, cek dulu datanya atau pakai email lain.

    ERROR 1406: Data too long for column

    ERROR 1406 (22001): Data too long for column 'status' at row 1

    Penyebab: nilai yang dimasukkan melebihi kapasitas kolom. Kolom status kita bertipe VARCHAR(20), jadi teks 21 karakter atau lebih ditolak. Di MySQL 8.4 dengan pengaturan bawaan (strict mode aktif), data tidak dipotong diam-diam tapi langsung ditolak, dan itu perilaku yang benar. Solusi: perpendek nilainya, atau kalau kebutuhan datanya memang lebih panjang, ubah kolom dengan ALTER TABLE pesanan MODIFY status VARCHAR(50) NOT NULL DEFAULT 'pending';.

    ERROR 1364: Field doesn’t have a default value

    ERROR 1364 (HY000): Field 'harga' doesn't have a default value

    Penyebab: ada kolom NOT NULL tanpa default yang tidak kamu sebut di daftar kolom. MySQL tidak tahu harus mengisi apa, jadi menolak. Solusi: sertakan kolom itu di perintah insert beserta nilainya. Ini alasan lain kenapa daftar kolom eksplisit itu penting, kamu langsung tahu kolom mana yang wajib diisi.

    ERROR 1136: Column count doesn’t match value count

    ERROR 1136 (21S01): Column count doesn't match value count at row 1

    Penyebab: jumlah nilai di VALUES tidak sama dengan jumlah kolom yang disebut. Biasanya gara-gara lupa satu nilai atau kelebihan koma saat menulis insert multi-baris yang panjang. Solusi: hitung ulang pasangan kolom dan nilai. Pesan errornya menyebut nomor baris yang bermasalah, mulai cek dari situ.

    Lanjut ke Bagian Berikutnya

    Dataset toko online kamu sekarang sudah terisi dan siap dipakai. Kamu sudah bisa insert data MySQL satu baris maupun banyak baris, paham apa yang terjadi pada kolom yang tidak diisi, dan tahu cara membaca error yang paling sering muncul.

    Data yang cuma bisa dimasukkan tapi tidak bisa dicari tentu belum berguna. Di bagian berikutnya, Belajar Database dari Nol #6: SELECT & WHERE, Query Dasar, kita mulai menggali data ini dengan query yang sesungguhnya, dari memilih kolom tertentu sampai menyaring baris dengan berbagai kondisi. Artikelnya terbit menyusul, pantau daftar lengkapnya di halaman hub Belajar Database.

    Referensi

  • Persiapan RAT Koperasi: Data yang Harus Siap Sebelum Hari H

    Persiapan RAT Koperasi: Data yang Harus Siap Sebelum Hari H

    RAT berjalan lancar kalau lima hal ini sudah siap sebelum hari H: laporan keuangan tahunan yang sudah diperiksa pengawas, laporan pertanggungjawaban pengurus, data simpanan dan pinjaman per anggota yang bisa dicek, usulan pembagian SHU, dan rencana kerja beserta anggaran tahun depan. Kelima dokumen itu adalah bahan utama Rapat Anggota Tahunan. Kalau semuanya beres jauh hari sebelum rapat, agenda tinggal berjalan satu per satu.

    Sebaliknya, RAT yang ricuh hampir selalu berawal dari data yang belum siap. Anggota bertanya, pengurus tidak bisa menjawab dengan angka, lalu suasana memanas. Artikel ini membahas apa saja yang perlu disiapkan, siapa yang mengerjakannya, dan kapan tenggatnya. Targetnya satu: rapat tahunan selesai dalam hitungan jam, bukan berlarut sampai malam.

    Kenapa RAT Sering Berujung Ricuh

    Sumber keributan di RAT sebenarnya bisa dihitung jari. Pola yang paling sering terjadi kurang lebih seperti ini.

    • Angka laporan tidak nyambung dengan catatan anggota. Anggota merasa simpanannya sekian, laporan pengurus bilang lain. Begitu satu orang protes soal saldo, anggota lain ikut curiga.
    • Pembagian SHU tidak bisa dijelaskan per orang. Total SHU disebutkan, tapi begitu ditanya “bagian saya berapa dan dari mana hitungannya”, pengurus tidak punya rinciannya.
    • Laporan disusun mepet, kadang semalam sebelum rapat. Hasilnya angka belum dicek silang, salah ketik di sana sini, dan pengurus sendiri tidak hafal isi laporannya.
    • Pertanyaan anggota dijawab “nanti kami cek dulu”. Sekali dua kali masih dimaklumi. Kalau berulang, anggota menangkap kesan pengurus tidak pegang data, dan dari situ kepercayaan runtuh.

    Perhatikan satu benang merahnya: semua masalah di atas adalah masalah data, bukan masalah forum. Rapatnya ricuh karena bahannya tidak siap. Jadi obatnya juga di persiapan, bukan di kelihaian moderator.

    Lima Dokumen yang Harus Siap Sebelum Hari H

    Mari bedah satu per satu: isinya apa, siapa yang menyiapkan, dan kapan harus selesai.

    1. Laporan keuangan tahunan yang sudah diperiksa pengawas

    Ini dokumen paling berat sekaligus paling penting. Isinya minimal neraca, perhitungan hasil usaha, arus kas, dan catatan atas laporan keuangan. Bendahara atau pengelola yang menyusun, pengawas yang memeriksa. Kata kuncinya “sudah diperiksa”. Laporan yang belum lewat pengawas rawan dipersoalkan di forum, karena anggota tidak punya jaminan angkanya sudah dicek pihak lain.

    2. Laporan pertanggungjawaban pengurus

    Kalau laporan keuangan bicara angka, laporan pertanggungjawaban bicara kerja. Isinya realisasi program tahun berjalan dibandingkan rencana yang disahkan RAT sebelumnya: mana yang tercapai, mana yang tidak, dan kenapa. Ketua dan sekretaris yang menyusun. Jujur di bagian ini justru menyelamatkan. Program yang gagal tapi dijelaskan alasannya jauh lebih mudah diterima daripada laporan yang semuanya terlihat mulus.

    3. Data simpanan dan pinjaman per anggota

    Ini dokumen yang paling sering dilupakan, padahal paling sering jadi pemicu ribut. Setiap anggota harus bisa mengecek saldo simpanan pokok, wajib, dan sukarelanya, plus sisa pinjaman kalau ada. Juru buku atau pengelola yang menyiapkan, dicocokkan dengan buku anggota sebelum rapat. Kalau ada selisih, selesaikan sebelum hari H, jangan di depan forum.

    4. Usulan pembagian SHU

    Alokasinya mengikuti AD/ART: berapa persen untuk cadangan, jasa anggota, dana pengurus, dan pos lainnya. Yang wajib disiapkan bukan cuma totalnya, tapi simulasi bagian per anggota berdasarkan jasa simpanan dan jasa usahanya. Rumus dan contoh perhitungannya sudah kami tulis lengkap di artikel cara menghitung SHU koperasi, jadi di sini cukup satu prinsip: setiap anggota harus bisa melihat angkanya sendiri dan cara angka itu muncul.

    5. Rencana kerja dan anggaran tahun depan

    RAT bukan cuma melihat ke belakang. Anggota juga mengesahkan rencana kerja dan rencana anggaran pendapatan dan belanja untuk tahun berikutnya. Pengurus yang menyusun, idealnya dengan masukan dari pengelola unit usaha. Buat yang realistis dan terukur. Rencana yang terlalu muluk akan jadi bahan tagihan di RAT tahun depan.

    Soal waktu, cara paling gampang adalah menghitung mundur dari tanggal RAT. Patokan yang umum dipakai: H-30 tutup buku dan semua transaksi tahun berjalan selesai dicatat, H-14 laporan selesai diperiksa pengawas, H-7 ringkasan laporan dibagikan ke anggota bersama undangan. Dengan jadwal ini, anggota datang ke rapat sudah membaca bahannya, bukan baru melihat angka di tempat.

    Transparansi per Anggota, Kunci Rapat yang Tenang

    Setiap anggota berhak tahu tiga angka miliknya: saldo simpanan, sisa pinjaman, dan bagian SHU yang akan diterimanya. Bukan sekadar hak formalitas. Anggota yang sudah tahu angkanya sendiri sebelum rapat tidak punya alasan curiga, dan rapat jadi fokus ke hal yang lebih besar seperti evaluasi usaha dan rencana tahun depan.

    Cara menyiapkannya tidak harus rumit. Yang masih manual bisa mencetak lembar rekap per anggota, satu halaman berisi tiga angka tadi plus riwayat singkatnya, dibagikan bersama undangan atau saat registrasi rapat. Koperasi yang sudah pakai sistem lebih enak lagi: anggota bisa mengecek sendiri lewat akses mandiri kapan pun, sehingga hari H tinggal konfirmasi, bukan verifikasi dari nol. Yang penting angkanya sama antara catatan koperasi dan yang dipegang anggota. Selisih satu angka saja di depan forum bisa memakan waktu satu jam.

    Pertanyaan Anggota yang Hampir Pasti Muncul

    Beberapa pertanyaan muncul hampir di setiap RAT. Lebih baik siapkan jawabannya dengan data dari sekarang.

    “Kenapa SHU tahun ini turun?” Jawab dengan perbandingan, bukan pembelaan. Tunjukkan pendapatan dan beban tahun ini berdampingan dengan tahun lalu, lalu tunjuk pos yang berubah. Misalnya pendapatan jasa pinjaman turun karena penyaluran berkurang, atau beban naik karena ada perbaikan kantor. Anggota bisa menerima SHU turun asalkan tahu sebabnya.

    “Uang simpanan kami diputar ke mana?” Ini pertanyaan soal kepercayaan. Siapkan rekap sederhana: berapa yang tersalur sebagai pinjaman anggota, berapa yang mengendap di kas dan bank, berapa yang jadi aset lain. Kalau komposisinya sehat dan bisa ditunjukkan, pertanyaan ini selesai dalam dua menit.

    “Kenapa bunga pinjaman segini?” Jelaskan dari struktur biayanya: jasa yang dibayar ke simpanan anggota, biaya operasional, risiko pinjaman macet, dan sisanya yang justru kembali ke anggota lewat SHU. Bandingkan juga dengan bunga di luar koperasi. Anggota sering lupa bahwa di koperasi, bunga yang mereka bayar sebagian kembali ke kantong mereka sendiri.

    Pola jawabannya sama semua: angka, pembanding, dan alasan. Selama tiga hal itu ada, pertanyaan sekeras apa pun tetap bisa dijawab dengan tenang.

    Tips Teknis Biar Rapat Singkat dan Terarah

    • Bagikan ringkasan, bukan tumpukan kertas. Laporan lengkap tetap tersedia bagi yang mau membaca, tapi yang dibagikan ke semua peserta cukup ringkasan dua sampai empat halaman berisi angka kunci. Peserta yang disodori laporan 60 halaman biasanya tidak membaca sama sekali, lalu bertanya hal yang sebenarnya sudah tertulis.
    • Sediakan sesi tanya jawab yang dicatat. Tunjuk satu orang khusus jadi notulis pertanyaan. Pertanyaan yang bisa dijawab langsung, jawab saat itu. Yang butuh penelusuran, catat dengan nama penanya dan tenggat jawabannya, lalu bacakan daftarnya sebelum rapat ditutup. Ini bedanya dengan “nanti kami cek dulu” yang menguap begitu saja.
    • Keputusan ditulis dan ditandatangani. Setiap keputusan rapat, dari pengesahan laporan sampai pembagian SHU, dibacakan ulang, dicatat di berita acara, dan ditandatangani pimpinan rapat beserta perwakilan anggota. Tanpa ini, keputusan yang sama bisa diperdebatkan ulang tahun depan.

    RAT yang Ringan Dimulai dari Pencatatan yang Rapi

    Kalau diperhatikan, hampir semua beban persiapan RAT sebenarnya adalah pekerjaan merapikan catatan setahun ke belakang. Koperasi yang pembukuannya berantakan butuh berbulan-bulan untuk merekap ulang transaksi, mencocokkan saldo anggota satu per satu, dan menambal selisih. Koperasi yang pencatatannya rapi sepanjang tahun menyiapkan semua dokumen di atas dalam hitungan hari, karena datanya tinggal ditarik.

    Di titik inilah sistem pencatatan digital terasa bedanya. Saldo simpanan dan pinjaman ter-update setiap ada transaksi, laporan keuangan bisa dilihat kapan saja, dan simulasi SHU tidak perlu dihitung manual di lembar kerja. Fitur-fitur yang relevan untuk pengurus sudah kami ulas di artikel aplikasi simpan pinjam koperasi. Salah satu contoh penerapannya bisa dilihat di studi kasus Koperasi Dwija Usaha yang kami bantu digitalisasi pencatatannya.

    Kalau koperasi Anda masih menghabiskan waktu berminggu-minggu setiap menjelang RAT hanya untuk merekap data, mungkin ini saat yang pas untuk membenahi sistemnya. Tim Arrazy terbuka untuk diskusi santai soal kebutuhan koperasi Anda, tanpa kewajiban apa pun.

    Pertanyaan yang Sering Diajukan

    Kapan sebaiknya RAT diselenggarakan?

    Praktik umum di Indonesia, RAT diselenggarakan paling lambat enam bulan setelah tutup tahun buku. Kebanyakan koperasi menggelarnya antara Januari sampai Maret. Makin dekat ke tutup buku makin baik, karena datanya masih segar dan keputusan untuk tahun berjalan tidak terlambat dieksekusi.

    Bagaimana kalau peserta rapat tidak memenuhi kuorum?

    Ketentuan kuorum diatur di AD/ART masing-masing koperasi. Umumnya, kalau kuorum tidak tercapai, rapat ditunda dalam jangka waktu tertentu dan rapat kedua bisa dianggap sah dengan jumlah kehadiran yang lebih longgar, sesuai aturan di AD/ART. Cara paling aman tetap di pencegahan: undangan disebar jauh hari beserta ringkasan materi, supaya anggota merasa penting untuk hadir.

    Dokumen apa saja yang perlu dibagikan ke anggota sebelum rapat?

    Minimal ringkasan laporan keuangan, ringkasan laporan pertanggungjawaban, usulan pembagian SHU, dan pokok-pokok rencana kerja tahun depan, dibagikan sekitar H-7 bersama undangan. Lembar saldo per anggota sebaiknya juga disertakan atau disediakan saat registrasi, supaya verifikasi angka pribadi selesai sebelum rapat dibuka.

  • Cara Masuk ke Container: docker exec, logs, dan Debugging

    Cara Masuk ke Container: docker exec, logs, dan Debugging

    Cara masuk ke container yang sedang jalan adalah dengan perintah docker exec -it nama_container bash. Kalau image-nya berbasis Alpine dan tidak punya bash, ganti dengan docker exec -it nama_container sh. Setelah itu kamu berada di dalam shell container, bisa lihat file, cek proses, dan edit konfigurasi seperti di server biasa.

    Artikel ini bagian kelima dari seri Belajar Docker dari Nol. Di sini kita bahas docker exec masuk container secara praktik: masuk ke container nginx, cari dan edit file konfigurasinya, lalu reload tanpa restart. Kita juga bahas cara memantau aplikasi lewat docker logs dan docker stats, mengambil file dengan docker cp, plus error klasik bash not found yang hampir pasti kamu temui.

    Di proyek klien, tim Arrazy hampir setiap hari memakai kombinasi exec dan logs ini untuk debug backend Go dan Laravel yang jalan di container. Ini skill dasar yang kepakai terus sampai level production.

    Prasyarat Sebelum Mulai

    Tutorial ini memakai Docker Engine 27.x di Ubuntu 24.04. Perintahnya sama persis di Windows dan macOS yang memakai Docker Desktop. Cek versimu dulu:

    docker --version

    Output yang diharapkan kurang lebih seperti ini:

    Docker version 27.5.1, build 9f9e405

    Kamu juga perlu paham konsep menjalankan container di background dan port mapping. Kalau belum, baca dulu bagian sebelumnya: Docker Port Mapping, Mode Detach, dan Environment Variable.

    docker exec -it: Masuk ke Shell Container yang Sedang Jalan

    Perintah docker exec menjalankan perintah tambahan di dalam container yang sudah berjalan. Formatnya:

    docker exec [opsi] nama_container perintah

    Dua opsi yang hampir selalu dipakai bersamaan:

    • -i (interactive): menjaga input tetap terbuka, jadi apa yang kamu ketik diteruskan ke container
    • -t (tty): memberi terminal virtual, jadi tampilannya seperti terminal normal lengkap dengan prompt

    Digabung jadi -it. Tanpa keduanya, shell memang jalan tapi kamu tidak bisa mengetik apa-apa. Contoh paling umum:

    docker exec -it web bash

    Perintah ini membuka shell bash di dalam container bernama web. Untuk keluar, ketik exit atau tekan Ctrl+D. Container tetap jalan setelah kamu keluar, karena yang berhenti hanya proses bash yang tadi kamu buka, bukan proses utama container.

    Bedanya docker exec dan docker run

    Pemula sering menukar dua perintah ini. Padahal fungsinya beda jauh:

    Aspek docker run docker exec
    Fungsi Membuat container baru dari image Menjalankan perintah di container yang sudah jalan
    Butuh apa Image Container berstatus running
    Efek Muncul container baru di docker ps Tidak ada container baru
    Contoh kasus Start nginx pertama kali Cek isi file config nginx yang sedang jalan

    Jadi kalau kamu menjalankan docker run -it nginx bash dengan niat “masuk ke nginx yang tadi”, yang terjadi justru Docker membuat container nginx kedua yang terpisah total. File yang kamu ubah di situ tidak akan berpengaruh ke container pertama. Ini sumber kebingungan yang sangat sering terjadi.

    Praktik: Masuk ke Container Nginx dan Edit Konfigurasinya

    Biar nempel, kita praktik langsung. Jalankan nginx versi 1.27 di background dengan port mapping:

    docker run -d --name web -p 8080:80 nginx:1.27

    Pastikan jalan:

    docker ps

    Kolom STATUS harus menunjukkan Up. Sekarang masuk ke dalamnya:

    docker exec -it web bash

    Prompt kamu berubah jadi seperti ini, tanda kamu sudah di dalam container:

    root@3f2a1b9c7d5e:/#

    Mencari file konfigurasi nginx

    Konfigurasi utama nginx ada di /etc/nginx/nginx.conf, dan konfigurasi per situs ada di /etc/nginx/conf.d/. Cek isinya:

    cat /etc/nginx/conf.d/default.conf

    Kamu akan lihat blok server yang listen di port 80 dan menyajikan file dari /usr/share/nginx/html.

    Edit konfigurasi tanpa editor

    Image nginx resmi itu ramping, tidak ada nano atau vim di dalamnya. Tapi ada sed, jadi kita bisa edit file langsung dari command line. Kita tambahkan header custom supaya perubahannya gampang dibuktikan:

    sed -i 's|listen       80;|listen       80;\n    add_header X-Diedit-Dari "dalam-container";|' /etc/nginx/conf.d/default.conf

    Validasi dulu sebelum reload. Ini kebiasaan wajib biar nginx tidak mati gara-gara typo:

    nginx -t

    Output yang diharapkan:

    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful

    Kalau sudah ok, reload nginx tanpa restart container:

    nginx -s reload

    Keluar dari container dengan exit, lalu tes dari mesin host:

    curl -I http://localhost:8080

    Di antara header respons, kamu akan melihat baris ini:

    X-Diedit-Dari: dalam-container

    Berhasil. Kamu baru saja masuk ke container yang sedang jalan, mengubah konfigurasinya, dan me-reload service tanpa downtime.

    Tidak harus buka shell dulu

    Untuk perintah sekali jalan, kamu tidak perlu masuk shell. Langsung saja:

    docker exec web nginx -t
    docker exec web cat /etc/nginx/conf.d/default.conf

    Pola ini enak dipakai di script atau saat cuma butuh satu informasi cepat.

    Memantau Aplikasi: docker logs, docker stats, dan docker cp

    Masuk ke container itu satu hal. Tapi sebagian besar sesi debugging justru dimulai dari membaca log dari luar.

    docker logs -f –tail: membaca log real time

    Container yang baik menulis log ke stdout dan stderr, dan Docker menangkap semuanya. Lihat log container nginx tadi:

    docker logs web

    Dua opsi yang paling sering dipakai:

    docker logs -f --tail 50 web
    • --tail 50: hanya tampilkan 50 baris terakhir, bukan seluruh riwayat log yang bisa ribuan baris
    • -f (follow): terus menampilkan log baru secara real time, seperti tail -f di Linux

    Coba buka http://localhost:8080 di browser sambil docker logs -f jalan. Setiap request langsung muncul sebagai baris access log baru. Tekan Ctrl+C untuk berhenti mengikuti log. Ini tidak mematikan container, hanya berhenti menonton.

    docker stats: cek pemakaian CPU dan memori

    Aplikasi lambat atau server penuh? Cek konsumsi resource semua container secara live:

    docker stats

    Outputnya kurang lebih:

    CONTAINER ID   NAME   CPU %   MEM USAGE / LIMIT     MEM %   NET I/O       BLOCK I/O   PIDS
    3f2a1b9c7d5e   web    0.00%   4.2MiB / 7.6GiB       0.05%   1.2kB/850B    0B/0B       5

    Dari sini kelihatan container mana yang rakus memori atau CPU. Tekan Ctrl+C untuk keluar. Kalau cuma mau snapshot sekali tanpa tampilan live, pakai docker stats --no-stream.

    docker cp: ambil file dari dalam container

    Kadang kamu butuh menyalin file dari container ke laptop, misalnya file konfigurasi atau log aplikasi yang ditulis ke file. Formatnya docker cp sumber tujuan:

    docker cp web:/etc/nginx/conf.d/default.conf ./default.conf

    File config tadi sekarang ada di folder kerjamu di host. Arah sebaliknya juga bisa, dari host ke container:

    docker cp ./default.conf web:/etc/nginx/conf.d/default.conf

    Setelah menyalin config baru ke dalam, jangan lupa docker exec web nginx -s reload supaya perubahan terbaca.

    Kapan docker exec Cocok untuk Debug, dan Apa Batasnya

    Gunakan docker exec untuk hal-hal yang sifatnya memeriksa dan bereksperimen: cek apakah file environment terbaca, tes koneksi ke database dari dalam container, lihat isi folder, atau coba ubah satu nilai config untuk membuktikan hipotesis. Cepat, tanpa rebuild, tanpa restart.

    Tapi ada satu hal penting yang wajib kamu pahami: semua perubahan lewat exec itu sementara. Perubahan tersimpan di writable layer milik container itu saja, bukan di image. Begitu container dihapus dan dibuat ulang dari image yang sama, misalnya lewat docker rm lalu docker run lagi, semua editanmu hilang dan kondisi kembali seperti bawaan image.

    Jadi alurnya yang sehat seperti ini:

    1. Pakai docker exec untuk menemukan akar masalah dan menguji perbaikan
    2. Setelah terbukti, pindahkan perbaikan itu ke tempat yang permanen: Dockerfile, file yang di-mount lewat volume, atau environment variable
    3. Rebuild atau recreate container, lalu verifikasi ulang

    Di tim Arrazy, aturan mainnya sama saat membangun sistem aplikasi untuk klien: exec hanya untuk investigasi, perbaikan permanen selalu masuk ke Dockerfile atau konfigurasi yang diversion-control. Kalau perbaikan cuma hidup di dalam container, satu kali server restart bisa mengembalikan bug yang sama dan tidak ada yang tahu kenapa.

    Troubleshooting: Error yang Sering Dialami Pemula

    OCI runtime exec failed: bash not found

    Kamu menjalankan docker exec -it nama_container bash lalu muncul:

    OCI runtime exec failed: exec failed: unable to start container process:
    exec: "bash": executable file not found in $PATH: unknown

    Penyebab: image-nya berbasis Alpine Linux, misalnya nginx:1.27-alpine atau redis:7-alpine. Alpine tidak menyertakan bash, adanya sh. Solusinya ganti perintahnya:

    docker exec -it nama_container sh

    Fungsinya sama saja untuk keperluan debug. Kalau ragu image apa yang dipakai container, cek dengan docker ps di kolom IMAGE.

    Container is not running

    Error response from daemon: container abc123 is not running

    Penyebab: docker exec hanya bisa dipakai ke container yang statusnya running. Kalau container-nya sudah exit, tidak ada proses yang bisa ditumpangi. Cek statusnya dengan docker ps -a. Kalau statusnya Exited, lihat dulu kenapa dia mati lewat docker logs nama_container, perbaiki penyebabnya, lalu start ulang dengan docker start nama_container sebelum exec.

    The input device is not a TTY

    the input device is not a TTY

    Penyebab paling umum: menjalankan docker exec -it dari Git Bash atau MinTTY di Windows, atau dari dalam script otomatis yang tidak punya terminal interaktif. Solusi di Git Bash: awali perintah dengan winpty, jadi winpty docker exec -it web bash. Kalau di dalam script atau cron, buang opsi -t dan jalankan perintah non-interaktif saja, misalnya docker exec web nginx -t.

    docker logs kosong padahal aplikasi jalan

    Kamu menjalankan docker logs tapi tidak ada output sama sekali. Penyebab yang paling sering: aplikasinya menulis log ke file di dalam container, bukan ke stdout/stderr. Docker hanya menangkap stdout dan stderr dari proses utama. Solusinya ada dua: konfigurasi aplikasi supaya log ke stdout (praktik standar untuk container), atau ambil file lognya dengan docker exec -it nama_container sh lalu cat, atau salin keluar pakai docker cp.

    Rangkuman dan Lanjut ke Bagian Berikutnya

    Sekarang kamu punya empat senjata utama untuk berurusan dengan container yang sedang jalan: docker exec -it untuk masuk ke shell, docker logs -f --tail untuk membaca log real time, docker stats untuk memantau resource, dan docker cp untuk memindahkan file. Kamu juga sudah paham kenapa perubahan lewat exec hilang setelah container dibuat ulang, dan kapan harus memindahkan perbaikan ke Dockerfile.

    Bagian berikutnya dari seri ini membahas “Mengelola Docker Image: Pull, Tag, dan Docker Hub”. Di sana kita bedah cara kerja tag image, kenapa latest itu jebakan, dan cara menyimpan image buatanmu sendiri. Artikelnya terbit menyusul, pantau daftarnya di halaman hub Belajar Docker.

    Referensi

  • Source Code Aplikasi Itu Milik Siapa: Klien atau Vendor?

    Source Code Aplikasi Itu Milik Siapa: Klien atau Vendor?

    Pertanyaan ini hampir selalu muncul di akhir proyek, padahal jawabannya ditentukan di awal. Source code aplikasi itu milik siapa? Jawaban jujurnya: tergantung apa yang tertulis di kontrak. Bukan tergantung siapa yang membayar, bukan juga siapa yang mengetik kodenya. Kalau kontrak Anda mengatur kepemilikan kode dengan jelas, urusan selesai. Kalau kontraknya diam, posisinya abu-abu, dan pengalaman kami bilang posisi abu-abu ini hampir selalu merugikan klien. Alasannya sederhana: kodenya secara fisik ada di tangan vendor.

    Maka aturan mainnya satu saja. Tulis soal kepemilikan source code di kontrak sebelum Anda membayar, bukan setelah aplikasi jadi. Artikel ini membahas tiga model kepemilikan yang umum dipakai, kenapa vendor kadang menahan kode, dan klausul apa saja yang perlu Anda minta. Kami menulis dari sisi orang dalam sebagai software house, jadi anggap ini pengalaman lapangan, bukan nasihat hukum.

    Kenapa jawabannya selalu kembali ke kontrak

    Banyak klien mengira logikanya seperti membeli barang. Saya bayar, berarti saya pemilik. Sayangnya software tidak bekerja seperti itu. Secara umum, hak cipta atas sebuah karya melekat pada pihak yang membuatnya, kecuali ada perjanjian yang mengalihkannya. Membayar biaya proyek tidak otomatis memindahkan kepemilikan kode ke tangan Anda.

    Di sinilah masalahnya. Sebagian besar kontrak pembuatan aplikasi yang kami lihat hanya mengatur lingkup kerja, harga, dan jadwal. Soal siapa pemilik kode setelah proyek selesai sering tidak disebut sama sekali. Selama hubungan baik, tidak ada yang mempersoalkan. Begitu ada perselisihan atau klien ingin pindah vendor, barulah semua pihak membuka kontrak dan menemukan kontraknya kosong di bagian paling penting.

    Kabar baiknya, ini mudah dicegah. Satu pasal yang jelas soal kepemilikan kode menyelesaikan sebagian besar potensi sengketa sebelum sempat terjadi.

    Tiga model kepemilikan yang umum dipakai

    Tidak ada satu model yang benar untuk semua proyek. Ketiganya sah, selama disepakati terbuka sejak awal. Yang salah adalah tidak membahasnya sama sekali.

    1. Full transfer ke klien

    Seluruh kode custom yang dibuat untuk proyek Anda menjadi milik Anda setelah pembayaran lunas. Anda bebas menyimpan, memodifikasi, atau menyerahkannya ke tim lain di kemudian hari. Ini model yang paling aman untuk klien, terutama kalau aplikasi itu bagian penting dari operasional bisnis Anda.

    Konsekuensinya, harga biasanya lebih tinggi. Vendor tidak bisa memakai ulang kode itu untuk klien lain, jadi seluruh biaya pengembangan dibebankan ke satu proyek. Vendor yang berpengalaman juga biasanya minta pengecualian untuk komponen atau library internal mereka, yaitu potongan kode yang sudah mereka pakai lintas proyek sejak sebelum Anda datang. Itu permintaan yang wajar, selama daftarnya jelas.

    2. Lisensi pakai

    Kode tetap milik vendor. Anda mendapat hak memakai aplikasinya, biasanya selamanya, tapi tidak boleh menjual ulang atau menyerahkan kodenya ke pihak lain. Model ini wajar untuk produk semi jadi, misalnya sistem yang sudah dipakai banyak klien lalu disesuaikan sedikit untuk kebutuhan Anda. Harganya lebih murah karena biaya pengembangan dibagi ke banyak pemakai.

    Kelemahannya jelas: Anda terikat ke vendor itu. Kalau suatu hari ingin pindah, Anda tidak bisa membawa kodenya. Untuk aplikasi pendukung yang mudah diganti, ini bisa diterima. Untuk sistem inti bisnis, pikirkan dua kali.

    3. Campuran dengan komponen open source

    Ini kenyataan di lapangan yang jarang dijelaskan ke klien. Hampir semua aplikasi modern dibangun di atas library open source, mulai dari framework sampai komponen kecil. Tidak ada vendor yang menulis semuanya dari nol, dan itu bukan masalah. Library open source punya lisensinya sendiri dan memang tidak bisa dialihkan ke siapa pun.

    Jadi ketika kontrak menyebut full transfer, yang sebenarnya dialihkan adalah kode custom yang ditulis khusus untuk proyek Anda. Komponen open source ikut terbawa dengan lisensi aslinya. Vendor yang jujur akan menjelaskan ini di depan, bukan memakainya sebagai celah untuk mengaburkan apa yang Anda terima.

    Kenapa vendor kadang menahan source code

    Supaya adil, tidak semua vendor yang menahan kode sedang berniat buruk. Ada beberapa alasan yang bisa dipahami.

    • Takut kodenya disalin lalu dipakai kompetitor atau dijual ulang tanpa izin.
    • Kode itu memang aset bisnis mereka, misalnya produk SaaS yang disewakan ke banyak klien.
    • Ada library internal yang menjadi pembeda mereka di pasar dan dipakai lintas proyek.

    Tapi ada juga alasan yang buruk: menahan kode supaya klien tidak bisa pindah ke mana-mana. Vendor jenis ini tahu bahwa selama kode di tangannya, klien terpaksa terus memakai jasanya berapa pun harganya.

    Cara membedakannya tidak sulit. Vendor dengan alasan wajar akan membahas kepemilikan kode sejak awal, menawarkan opsi, dan bersedia menuliskannya di kontrak. Batasannya jelas: bagian ini milik Anda, bagian itu milik kami, dan ini alasannya. Sebaliknya, penyanderaan hampir selalu punya pola yang sama. Topik ini tidak pernah dibahas di awal, kontraknya diam, lalu syarat dan biaya baru muncul tepat ketika Anda ingin pindah. Kalau sebuah alasan baru terdengar saat Anda mau keluar, itu bukan alasan. Itu alat tawar.

    Yang harus tertulis di kontrak

    Anda tidak perlu kontrak setebal buku. Beberapa poin ini saja sudah menutup sebagian besar risiko.

    • Siapa pemilik kode custom setelah lunas. Sebut jelas: seluruh kode yang ditulis khusus untuk proyek ini beralih ke klien setelah pembayaran penuh. Kalau ada pengecualian untuk library internal vendor, minta daftarnya ditulis.
    • Akses repository. Minimal klien punya akses read ke repository selama proyek berjalan, atau menerima salinan berkala. Jangan menunggu serah terima akhir untuk pertama kali melihat kode Anda sendiri.
    • Komponen berlisensi pihak ketiga. Kalau aplikasi memakai layanan atau library berbayar, tulis siapa yang menanggung biaya perpanjangannya dan atas nama siapa akunnya terdaftar.
    • Skenario vendor tutup. Apa yang terjadi pada kode kalau vendor berhenti beroperasi. Untuk proyek besar, ada mekanisme bernama source code escrow, yaitu salinan kode dititipkan ke pihak ketiga netral dan diserahkan ke klien pada kondisi tertentu. Untuk proyek kecil ini berlebihan, tapi untuk sistem bernilai besar layak disebut.

    Kalau vendor keberatan dengan semua poin di atas tanpa mau menawarkan jalan tengah, itu sinyal yang perlu Anda perhatikan sebelum tanda tangan.

    Kasus yang paling sering kami dengar

    Polanya hampir selalu sama. Sebuah bisnis memakai aplikasi yang dibuat vendor beberapa tahun lalu. Layanan vendor menurun, atau harganya naik terus, dan klien memutuskan pindah. Vendor baru bertanya hal pertama yang wajar: mana source code-nya. Ternyata tidak ada. Klien menghubungi vendor lama, dan vendor lama memasang harga untuk menyerahkan kode. Kadang angkanya mendekati biaya membangun ulang dari nol.

    Posisi klien di titik ini lemah. Kontrak lama diam soal kepemilikan, kode ada di tangan vendor, dan aplikasi harus tetap jalan setiap hari. Kebanyakan akhirnya membayar, atau membangun ulang dari awal. Dua-duanya mahal, dan dua-duanya bisa dicegah dengan satu klausul yang ditulis bertahun-tahun sebelumnya, saat hubungan masih baik dan tidak ada yang merasa terancam.

    Praktik serah terima yang sehat

    Kepemilikan di atas kertas saja belum cukup. Bentuk serah terimanya juga menentukan seberapa berguna kode itu di tangan Anda.

    Minta serah terima lewat repository git lengkap dengan riwayat perubahannya, bukan file zip yang dilempar sekali di akhir proyek. Riwayat commit membantu tim mana pun yang meneruskan aplikasi memahami bagaimana kode itu berkembang. File zip tanpa riwayat memang secara teknis adalah source code, tapi nilainya jauh lebih rendah untuk pengembangan jangka panjang.

    Biasakan juga menyimpan salinan sendiri setiap ada rilis besar. Tidak perlu rumit, cukup pastikan ada cadangan kode di penyimpanan yang Anda kendalikan, bukan hanya di server vendor. Kalau suatu hari terjadi apa pun pada vendor, aplikasi Anda tidak ikut hilang.

    Di Arrazy sendiri posisinya sederhana: kode custom yang kami tulis untuk klien menjadi milik klien setelah pembayaran lunas, dan serah terima dilakukan lewat repository. Kalau Anda sedang merencanakan pembuatan sistem atau aplikasi dan ingin membahas skema kepemilikan kodenya sejak awal, hubungi kami dan kita bicarakan sebelum kontrak dibuat.

    Pertanyaan yang sering muncul

    Kontrak saya telanjur tidak mengatur soal ini. Bagaimana?

    Ajak vendor membuat addendum atau berita acara yang menegaskan kepemilikan dan serah terima kode. Lakukan sekarang, selagi hubungan masih baik, bukan saat Anda sudah berniat pindah. Kebanyakan vendor yang sehat mau menandatanganinya karena isinya memang menegaskan praktik yang wajar.

    Apakah boleh minta source code di tengah proyek?

    Boleh, dan sebaiknya memang begitu. Akses read ke repository atau salinan berkala di tiap termin pembayaran adalah permintaan yang normal. Vendor yang bekerja rapi tidak keberatan, karena kode yang mereka serahkan bertahap sama dengan kode yang akan diserahkan di akhir.

    Database termasuk source code bukan?

    Secara teknis bukan, tapi jawabannya lebih tegas: data itu milik Anda. Data pelanggan, transaksi, dan seluruh isi database berasal dari operasional bisnis Anda, apa pun model kepemilikan kodenya. Vendor mana pun tidak punya alasan menahan data Anda, dan Anda berhak minta salinan database kapan saja dalam format yang bisa dipakai ulang.

    Kepemilikan source code hanyalah satu bagian dari serah terima proyek yang beres. Akses hosting, domain, dokumentasi, dan akun layanan pihak ketiga juga sering tercecer. Daftar lengkapnya sudah kami tulis di checklist serah terima proyek aplikasi dari vendor. Baca sebelum proyek Anda dinyatakan selesai.

  • Cara Membuat Link WhatsApp wa.me dengan Pesan Otomatis Terisi

    Cara Membuat Link WhatsApp wa.me dengan Pesan Otomatis Terisi

    Cara membuat link WhatsApp itu sebenarnya sederhana. Formatnya https://wa.me/ diikuti nomor dalam format internasional. Kalau mau pesannya langsung terisi saat diklik, tambahkan parameter ?text= di belakangnya. Ini contoh utuh yang bisa langsung Anda tiru:

    https://wa.me/6281234567890?text=Halo%2C%20saya%20mau%20tanya%20produk%20ini

    Saat link itu diklik, aplikasi WhatsApp terbuka dan kolom chat sudah berisi tulisan “Halo, saya mau tanya produk ini”. Pelanggan tinggal menekan tombol kirim. Ganti nomornya dengan nomor WhatsApp usaha Anda, ganti teksnya sesuai kebutuhan, selesai. Di bawah ini kita bahas detailnya satu per satu. Mulai dari aturan penulisan nomor yang sering salah, cara menyusun pesan otomatis yang benar, contoh siap pakai, sampai tempat terbaik memasang link ini.

    Aturan penulisan nomor, di sini kesalahan paling sering terjadi

    Bagian nomor adalah sumber masalah nomor satu. Banyak orang menulis nomornya persis seperti yang tersimpan di kontak HP, lalu bingung kenapa linknya tidak jalan. Aturannya begini:

    • Pakai format internasional. Untuk Indonesia, awali dengan kode negara 62.
    • Hilangkan angka 0 di depan. Nomor 0812 3456 7890 ditulis menjadi 6281234567890.
    • Jangan pakai tanda plus. Bukan +6281234567890, cukup 6281234567890.
    • Jangan ada spasi, strip, atau tanda kurung sama sekali.

    Jadi kalau nomor WhatsApp toko Anda 0857-1234-5678, link dasarnya adalah https://wa.me/6285712345678. Aturan yang sama berlaku untuk negara lain, tinggal ganti 62 dengan kode negara masing-masing. Sebelum disebar ke mana-mana, klik dulu link itu dari HP lain untuk memastikan chat terbuka ke nomor yang benar. Verifikasi satu menit ini menyelamatkan Anda dari link rusak yang terlanjur dicetak di ribuan brosur.

    Menambahkan pesan otomatis dengan parameter ?text=

    Setelah link dasar jadi, tambahkan ?text= lalu isi pesannya. Satu hal penting: URL tidak boleh mengandung spasi. Setiap spasi harus diganti dengan kode %20. Tanda baca tertentu juga punya kode sendiri, misalnya koma menjadi %2C dan tanda tanya menjadi %3F. Proses ini disebut URL encoding.

    Tidak perlu hafal semua kodenya. Anda bisa pakai dua jalan pintas:

    • Tool URL encoder online. Tempel kalimat biasa, tool ini mengubahnya jadi teks ter-encode yang siap ditempel di belakang ?text=.
    • Generator link WhatsApp. Cukup masukkan nomor dan pesan dalam bahasa biasa, generator langsung mengeluarkan link jadi. Cari saja dengan kata kunci “WhatsApp link generator”, pilihannya banyak dan rata-rata gratis.

    Kalau mau menulis manual pun tetap bisa. Yang penting spasi jadi %20 dan hasil akhirnya dites dulu dengan diklik sendiri.

    Contoh pesan siap pakai, tinggal ganti nomornya

    Berikut tiga pola pesan yang paling sering dipakai usaha kecil. Setiap contoh ada versi terbaca dan versi link yang sudah di-encode.

    1. Tanya produk

    Pesan: “Halo, saya mau tanya produk yang ada di katalog”

    https://wa.me/6281234567890?text=Halo%2C%20saya%20mau%20tanya%20produk%20yang%20ada%20di%20katalog

    2. Konfirmasi pesanan dari katalog

    Pesan: “Halo, saya mau pesan produk kode A12 dari katalog. Apakah masih tersedia?”

    https://wa.me/6281234567890?text=Halo%2C%20saya%20mau%20pesan%20produk%20kode%20A12%20dari%20katalog.%20Apakah%20masih%20tersedia%3F

    Pola ini enak dipakai kalau katalog Anda punya kode produk. Buat satu link per produk, ganti bagian kodenya saja. Admin langsung tahu barang mana yang dimaksud tanpa tanya balik.

    3. Tanya jadwal layanan

    Pesan: “Halo, saya mau tanya jadwal layanan untuk minggu ini”

    https://wa.me/6281234567890?text=Halo%2C%20saya%20mau%20tanya%20jadwal%20layanan%20untuk%20minggu%20ini

    Cocok untuk salon, bengkel, klinik, jasa servis, atau usaha apa pun yang jalan dengan sistem booking.

    Kenapa pesan awal yang sudah terisi itu penting

    Mungkin terlihat sepele. Toh pelanggan bisa mengetik sendiri. Praktiknya tidak sesederhana itu, dan ada tiga alasan kenapa pesan terisi otomatis layak diseriusi.

    Pertama, pelanggan malas mengetik pembuka. Chat kosong itu canggung. Banyak orang membuka chat, bingung mau menulis apa, lalu menutupnya lagi. Kalau pesannya sudah terisi, hambatan itu hilang. Satu klik, satu tombol kirim, percakapan dimulai.

    Kedua, admin langsung tahu konteks. Pesan “Halo, saya mau pesan produk kode A12” jauh lebih mudah ditangani daripada pesan “P” atau “Halo gan”. Admin tidak perlu tanya balik, pelanggan tidak perlu menunggu, dan percakapan lebih cepat sampai ke transaksi.

    Ketiga, pesan bisa jadi penanda sumber. Ini trik yang jarang dipakai padahal berguna. Kalau link di bio Instagram berisi teks “Halo, saya lihat promo di Instagram”, setiap chat yang masuk dengan kalimat itu jelas datang dari Instagram. Anda jadi tahu kanal mana yang benar-benar mendatangkan pelanggan tanpa perlu tool analitik apa pun.

    Di mana saja link wa.me ini dipasang

    Link yang sudah jadi baru berguna kalau dipasang di tempat yang dilihat pelanggan. Beberapa lokasi yang paling masuk akal:

    • Bio Instagram dan TikTok. Ini tempat paling umum. Kalau Anda punya beberapa link sekaligus, wa.me bisa jadi salah satu tombol di halaman link bio. Cara menyusunnya pernah kami bahas di artikel cara membuat link bio Instagram untuk bisnis.
    • Katalog PDF. Sisipkan link di setiap halaman produk. Pembaca katalog tinggal klik dan langsung terhubung ke admin dengan konteks produk yang jelas.
    • Website. Bisa berupa tombol “Hubungi Kami”, bisa juga tombol WhatsApp mengambang yang menempel di pojok layar dan ikut ke mana pun pengunjung scroll.
    • QR code. Link wa.me bisa diubah jadi QR code lewat tool QR code generator, banyak yang gratis di internet. Hasilnya cocok dicetak di kemasan produk, banner toko, meja kasir, atau kartu nama. Pelanggan tinggal scan, chat langsung terbuka.

    Satu catatan untuk versi cetak. Setelah desain kemasan atau banner jadi, scan dulu QR code dari hasil cetak percobaan, bukan hanya dari layar. Kadang QR yang terlalu kecil atau tercetak buram jadi susah dibaca kamera HP. Lebih baik ketahuan di satu lembar percobaan daripada di satu rim brosur.

    Trik lanjutan dan batasan yang perlu Anda tahu

    Dua trik sederhana untuk memaksimalkan link WhatsApp Anda. Pertama, buat link berbeda untuk tiap kanal. Teks “Halo, saya dari Instagram” untuk bio Instagram, “Halo, saya lihat brosur” untuk media cetak, dan seterusnya. Dari isi chat yang masuk, Anda langsung tahu kanal mana yang paling ramai. Kedua, kalau link mau dicetak atau dibacakan, singkatkan dulu pakai layanan URL shortener. Link pendek lebih enak diketik ulang dan lebih rapi di materi cetak.

    Sekarang batasannya, dan ini perlu dikatakan jujur. Link wa.me hanya membuka chat ke satu nomor. Anda tidak bisa mengatur chat masuk ke admin tertentu, tidak bisa membagi antrean ke beberapa CS, dan tidak bisa membuat balasan otomatis yang memahami isi pertanyaan. Untuk balasan otomatis sederhana, fitur bawaan WhatsApp Business sudah cukup, contoh kalimatnya bisa dilihat di artikel contoh auto-reply WhatsApp bisnis yang tidak kaku. Tapi kalau kebutuhan Anda sudah sampai ke pembagian chat ke banyak admin atau balasan pintar yang bisa menjawab pertanyaan produk, itu sudah wilayah chatbot WhatsApp dengan AI, bukan wilayah link biasa.

    Pertanyaan yang sering muncul

    Apa bedanya wa.me dan api.whatsapp.com?

    Arahnya sama saja. Link https://api.whatsapp.com/send?phone=6281234567890&text=... adalah format lama yang masih berfungsi, sedangkan wa.me adalah versi pendeknya yang resmi dari WhatsApp. Keduanya membuka chat ke nomor yang sama. Untuk keperluan sehari-hari, pakai wa.me saja karena lebih singkat dan mudah diingat.

    Apakah link ini bisa untuk WhatsApp biasa, bukan WhatsApp Business?

    Bisa. Link wa.me bekerja untuk nomor WhatsApp apa pun, baik akun pribadi maupun akun Business. Yang penting nomornya aktif dan terdaftar di WhatsApp.

    Kenapa link saya tidak jalan?

    Sembilan dari sepuluh kasus, masalahnya di format nomor. Cek lagi: apakah masih ada angka 0 di depan, tanda plus, spasi, atau strip. Nomor harus ditulis bersih seperti 6281234567890. Kalau nomor sudah benar tapi pesan tidak muncul, cek bagian teksnya. Pastikan tidak ada spasi mentah dan semua karakter sudah di-encode dengan benar.

    Penutup

    Membuat link WhatsApp dengan pesan otomatis cuma butuh dua bahan: nomor dalam format internasional dan teks yang sudah di-encode. Lima menit selesai, dan efeknya terasa setiap hari. Pelanggan lebih mudah memulai chat, admin lebih cepat paham konteks, dan Anda bisa tahu dari kanal mana mereka datang. Mulai dari satu link dulu untuk bio media sosial Anda, tes kliknya, lalu tambahkan versi lain untuk katalog dan materi cetak seiring kebutuhan.

  • Belajar Golang dari Nol #27: Validasi Input, Jangan Percaya Data Luar

    Belajar Golang dari Nol #27: Validasi Input, Jangan Percaya Data Luar

    Di Belajar Golang dari Nol #26 kita sudah mengatur siapa boleh melakukan apa lewat role dan otorisasi. Server kita sekarang tahu membedakan admin dan kasir. Tapi ada satu lubang yang belum kita tutup rapat. Bagaimana kalau data yang dikirim user itu sendiri yang bermasalah? Email tanpa tanda @. Password satu huruf. Field harga yang tidak pernah dikirim tapi diam-diam terbaca nol. Di bagian ini kita bereskan semuanya secara sistematis. Kalau kamu baru bergabung, mulai dari daftar lengkap seri dulu supaya tidak ada yang terlewat.

    Semua Data dari Luar Itu Tersangka

    Ini prinsip pertama dan paling penting. Server kamu tidak boleh percaya pada apa pun yang datang dari luar. Body JSON bisa diisi field aneh. Query param bisa berisi angka negatif. Path param bisa berisi string padahal kamu berharap angka. Header bisa dipalsukan. File upload bisa menyamar, seperti yang sudah kita bahas waktu memvalidasi file di bagian 20.

    Kenapa harus separanoid itu? Karena request ke API kamu tidak selalu datang dari frontend yang kamu buat. Siapa pun bisa membuka terminal, mengetik perintah curl, dan mengirim apa saja ke endpoint kamu. Validasi di sisi frontend itu bagus untuk pengalaman user, tapi dari sudut pandang server, validasi frontend itu tidak ada. Anggap saja tidak pernah terjadi.

    Prinsip kedua: validasi dilakukan di tepi, bukan berserakan. Maksudnya, begitu data masuk lewat handler, langsung periksa di situ. Jangan biarkan data mentah menyebar ke service, repository, lalu baru ketahuan bermasalah saat sudah setengah jalan diproses. Selama seri ini kita sudah beberapa kali menulis validasi kecil di sana-sini. Sekarang kita rapikan jadi satu pola yang konsisten.

    Jebakan Decode JSON yang Diam-Diam

    Decoder JSON bawaan Go itu sopan. Terlalu sopan, malah. Dia tidak protes kalau ada field yang salah ketik. Coba lihat struct ini.

    type ProdukRequest struct {
    	Nama  string `json:"nama"`
    	Harga int    `json:"harga"`
    }

    Sekarang bayangkan frontend mengirim body seperti ini, dengan typo di field harga.

    {"nama": "Kopi Susu", "hrga": 15000}

    Decode berhasil tanpa error. Field hrga diabaikan begitu saja, dan Harga terisi nol karena itu zero value untuk int. Masih ingat konsep zero value dari bagian 5? Di sinilah dia menggigit balik. Produk seharga lima belas ribu tersimpan sebagai produk gratis, dan tidak ada satu pun error yang muncul.

    Masalah kedua lebih halus lagi. Kalau Harga bernilai nol, kamu tidak bisa membedakan dua kemungkinan: apakah user memang mengirim nol karena produknya gratis, atau field itu tidak dikirim sama sekali? Keduanya menghasilkan nilai yang sama persis.

    Ada tiga senjata untuk menutup jebakan ini. Pertama, DisallowUnknownFields supaya field yang tidak dikenal langsung ditolak. Kedua, pointer untuk membedakan kosong dan nol. Ketiga, batas ukuran body supaya orang tidak bisa mengirim payload raksasa, sama semangatnya dengan batas ukuran file upload di bagian 20.

    type ProdukRequest struct {
    	Nama  string `json:"nama"`
    	Harga *int   `json:"harga"`
    }
    
    func decodeJSON(w http.ResponseWriter, r *http.Request, dst any) error {
    	r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // maksimal 1 MB
    	dec := json.NewDecoder(r.Body)
    	dec.DisallowUnknownFields()
    	return dec.Decode(dst)
    }

    Dengan Harga bertipe *int, ceritanya jadi jelas. Kalau field tidak dikirim, nilainya nil. Kalau dikirim nol, nilainya pointer ke angka nol. Dua kondisi yang tadinya menyamar jadi satu, sekarang bisa dibedakan. Dan karena decodeJSON ini kita jadikan helper, semua handler memakai aturan yang sama tanpa harus mengulang kode.

    Validasi Manual yang Rapi

    Setelah decode berhasil, giliran memeriksa isinya. Pola yang enak dipakai: setiap struct request punya method Validate yang mengembalikan daftar error per field. Bukan satu error gabungan, tapi map yang memetakan nama field ke pesan kesalahannya.

    import (
    	"net/mail"
    	"strings"
    )
    
    type RegisterRequest struct {
    	Nama     string `json:"nama"`
    	Email    string `json:"email"`
    	Password string `json:"password"`
    }
    
    func (req RegisterRequest) Validate() map[string]string {
    	errs := map[string]string{}
    
    	if strings.TrimSpace(req.Nama) == "" {
    		errs["nama"] = "nama wajib diisi"
    	}
    	if _, err := mail.ParseAddress(req.Email); err != nil {
    		errs["email"] = "format email tidak valid"
    	}
    	if len(req.Password) < 8 {
    		errs["password"] = "password minimal 8 karakter"
    	}
    
    	if len(errs) > 0 {
    		return errs
    	}
    	return nil
    }

    Kenapa map, bukan error biasa? Karena frontend butuh tahu field mana yang salah supaya bisa menampilkan pesan tepat di bawah kolom isian yang bermasalah. Balasannya kita bungkus dalam JSON dengan status 400.

    func kirimErrorValidasi(w http.ResponseWriter, errs map[string]string) {
    	w.Header().Set("Content-Type", "application/json")
    	w.WriteHeader(http.StatusBadRequest)
    	json.NewEncoder(w).Encode(map[string]any{"errors": errs})
    }

    Hasilnya seperti ini. Satu request, semua kesalahan dilaporkan sekaligus, bukan satu per satu.

    {
      "errors": {
        "email": "format email tidak valid",
        "password": "password minimal 8 karakter"
      }
    }

    Bandingkan dengan API yang hanya membalas “input tidak valid” tanpa penjelasan. User mengisi ulang form sambil menebak-nebak salahnya di mana. Format per field ini kecil usahanya, besar dampaknya.

    go-playground/validator, Jalan Singkat yang Populer

    Menulis validasi manual untuk tiga field itu ringan. Tapi kalau aplikasimu punya dua puluh struct request dengan aturan mirip-mirip, kode Validate mulai terasa berulang. Di sinilah library go-playground/validator membantu. Aturan ditulis sebagai tag di struct.

    import "github.com/go-playground/validator/v10"
    
    type ProdukRequest struct {
    	Nama  string `json:"nama" validate:"required,min=3,max=100"`
    	Email string `json:"email" validate:"required,email"`
    	Stok  int    `json:"stok" validate:"gte=0"`
    }
    
    var validate = validator.New()

    Tag required menolak nilai kosong, email memeriksa format, min dan max membatasi panjang, gte=0 memastikan angka tidak negatif. Satu baris tag menggantikan beberapa baris if.

    Masalahnya, pesan error bawaannya berbahasa mesin. Sesuatu seperti “Field validation for ‘Email’ failed on the ’email’ tag” jelas tidak pantas ditampilkan ke user. Kita terjemahkan sendiri jadi bahasa manusia, sekaligus dipetakan per field seperti pola sebelumnya.

    func pesanValidasi(err error) map[string]string {
    	errs := map[string]string{}
    	for _, e := range err.(validator.ValidationErrors) {
    		field := strings.ToLower(e.Field())
    		switch e.Tag() {
    		case "required":
    			errs[field] = field + " wajib diisi"
    		case "email":
    			errs[field] = "format email tidak valid"
    		case "min":
    			errs[field] = field + " minimal " + e.Param() + " karakter"
    		case "gte":
    			errs[field] = field + " tidak boleh kurang dari " + e.Param()
    		default:
    			errs[field] = field + " tidak valid"
    		}
    	}
    	return errs
    }

    Lalu kapan cukup manual dan kapan pakai library? Jujur saja: untuk aplikasi kecil dengan sedikit endpoint, validasi manual lebih mudah dibaca dan tidak menambah dependensi. Library mulai terasa manfaatnya saat jumlah struct request banyak dan aturannya standar. Yang penting bukan pilihan alatnya, tapi konsistensinya. Pilih satu pola, pakai di semua endpoint.

    Bentuk Data vs Aturan Bisnis

    Ada dua jenis validasi yang sering dicampur padahal tempatnya beda. Yang pertama validasi bentuk: apakah email formatnya benar, apakah password cukup panjang, apakah stok berupa angka. Ini urusan handler, karena tidak butuh melihat database atau kondisi sistem.

    Yang kedua validasi aturan bisnis: apakah stok masih cukup kalau dikurangi jumlah pesanan, apakah email ini sudah dipakai akun lain, apakah user ini boleh menghapus data itu. Aturan seperti ini butuh konteks, biasanya butuh query ke database, dan tempatnya di layer service, sesuai pembagian tanggung jawab yang kita susun di bagian 16 dan kita praktikkan lagi di bagian 23.

    Contoh konkret. “Stok tidak boleh minus” terdengar seperti validasi input, tapi sebenarnya bukan. Angka 5 itu bentuknya valid. Baru setelah service mengecek stok tersisa 3, ketahuan bahwa mengurangi 5 akan membuat stok minus. Handler tidak mungkin tahu itu. Jadi jangan paksakan semua pemeriksaan masuk ke Validate. Bentuk di handler, aturan bisnis di service. Pembagian ini membuat masing-masing lapisan tetap sederhana.

    Sanitasi: Bereskan Saat Keluar, Bukan Saat Masuk

    Sekarang topik yang sering disalahpahami. Banyak pemula berpikir input berbahaya harus dibersihkan sebelum disimpan, misalnya menghapus semua tag HTML dari nama user. Pendekatan yang lebih sehat justru sebaliknya: simpan apa adanya, lalu escape saat menampilkan.

    Sebentar, apa itu XSS? Bayangkan ada user jahil yang mengisi nama dengan potongan kode JavaScript. Kalau aplikasimu menampilkan nama itu di halaman web mentah-mentah, browser pengunjung lain akan menjalankan kode tersebut seolah bagian sah dari situsmu. Kode itu bisa mencuri sesi, membajak akun, atau mengarahkan korban ke situs palsu. Itulah XSS, singkatan dari cross-site scripting: skrip orang lain yang menumpang lewat data.

    Kabar baiknya, html/template yang kita pakai sejak bagian 21 sudah melakukan escaping otomatis. Setiap nilai yang dirender lewat template itu diubah jadi teks aman, sehingga kode jahat tampil sebagai tulisan biasa, bukan dieksekusi. Ini alasan yang sama kenapa kita pakai placeholder untuk SQL di bagian 12: biarkan alat yang tepat menangani escaping, jangan tempel string sendiri.

    Lalu kenapa API yang hanya membalas JSON tetap perlu peduli? Karena browser kadang sok tahu. Kalau response tidak diberi Content-Type yang jelas, sebagian browser mencoba menebak jenis kontennya, dan tebakan itu bisa berujung pada JSON yang diperlakukan sebagai HTML. Pastikan setiap response JSON menyertakan header Content-Type: application/json, seperti yang sudah kita lakukan di helper tadi. Murah, satu baris, dan menutup satu pintu serangan.

    Normalisasi, Validasi Kecil yang Mencegah Bug Besar

    Ada satu langkah lagi sebelum data disimpan: normalisasi. Ini bukan soal menolak data, tapi merapikan bentuknya supaya konsisten. Tiga contoh yang paling sering menyelamatkan saya.

    Pertama, strings.TrimSpace untuk semua input teks. Spasi tak sengaja di akhir nama atau email itu sangat umum, apalagi dari keyboard HP yang suka menambah spasi setelah autocomplete.

    Kedua, email di-lowercase sebelum dicek unik. Waktu membangun register di bagian 25, kita menyimpan email sebagai identitas login. Kalau Budi@contoh.com dan budi@contoh.com dianggap dua akun berbeda, user akan bingung kenapa tidak bisa login padahal merasa sudah daftar. Normalkan dulu, baru bandingkan.

    Ketiga, nomor HP dinormalkan ke awalan 62. User Indonesia menulis nomor dengan segala gaya: 0812, +62812, 62812, kadang pakai spasi atau strip. Kalau disimpan apa adanya, fitur kirim WhatsApp atau pencarian nomor akan kacau.

    func normalisasiHP(hp string) string {
    	hp = strings.TrimSpace(hp)
    	hp = strings.ReplaceAll(hp, " ", "")
    	hp = strings.ReplaceAll(hp, "-", "")
    
    	switch {
    	case strings.HasPrefix(hp, "+62"):
    		return hp[1:]
    	case strings.HasPrefix(hp, "0"):
    		return "62" + hp[1:]
    	}
    	return hp
    }

    Letakkan normalisasi tepat sebelum validasi. Urutannya: decode, normalisasi, validasi bentuk, baru serahkan ke service. Dengan begitu validasi bekerja pada data yang sudah rapi.

    Latihan: Memperkuat Endpoint Register

    Sekarang kita gabungkan semuanya ke endpoint register yang kita buat di bagian 25. Dulu validasinya masih seadanya. Versi barunya membatasi body, menolak field asing, menormalkan input, lalu melaporkan error per field.

    func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
    	var req RegisterRequest
    	if err := decodeJSON(w, r, &req); err != nil {
    		kirimErrorValidasi(w, map[string]string{
    			"body": "format JSON tidak valid atau ada field yang tidak dikenal",
    		})
    		return
    	}
    
    	// normalisasi dulu, baru validasi
    	req.Nama = strings.TrimSpace(req.Nama)
    	req.Email = strings.ToLower(strings.TrimSpace(req.Email))
    
    	if errs := req.Validate(); errs != nil {
    		kirimErrorValidasi(w, errs)
    		return
    	}
    
    	user, err := h.service.Register(r.Context(), req)
    	if err != nil {
    		// aturan bisnis, misalnya email sudah terdaftar,
    		// ditangani service seperti di bagian 25
    		kirimErrorValidasi(w, map[string]string{"email": err.Error()})
    		return
    	}
    
    	w.Header().Set("Content-Type", "application/json")
    	w.WriteHeader(http.StatusCreated)
    	json.NewEncoder(w).Encode(user)
    }

    Uji dengan curl. Pertama, request yang benar.

    curl -X POST http://localhost:8080/register \
      -H "Content-Type: application/json" \
      -d '{"nama":"Budi Santoso","email":"Budi@contoh.com","password":"rahasia123"}'

    Server membalas 201, dan email tersimpan sebagai budi@contoh.com berkat normalisasi. Sekarang tiga variasi gagal. Password terlalu pendek dan nama kosong sekaligus.

    curl -X POST http://localhost:8080/register \
      -H "Content-Type: application/json" \
      -d '{"nama":"  ","email":"budi@contoh.com","password":"123"}'

    Balasan 400 dengan dua error sekaligus: nama wajib diisi, password minimal 8 karakter. Perhatikan nama berisi spasi saja tetap tertangkap karena TrimSpace berjalan sebelum validasi. Berikutnya, email rusak.

    curl -X POST http://localhost:8080/register \
      -H "Content-Type: application/json" \
      -d '{"nama":"Budi","email":"bukan-email","password":"rahasia123"}'

    Balasan 400 dengan pesan format email tidak valid. Terakhir, field yang salah ketik.

    curl -X POST http://localhost:8080/register \
      -H "Content-Type: application/json" \
      -d '{"nama":"Budi","emial":"budi@contoh.com","password":"rahasia123"}'

    Tanpa DisallowUnknownFields, typo emial akan lolos diam-diam dan email tersimpan kosong. Sekarang decoder langsung menolak, dan frontend tahu ada yang salah sejak detik pertama, bukan setelah data aneh masuk database.

    Penutup

    Hari ini kita menutup satu lubang besar dengan pola yang konsisten: semua data dari luar itu tersangka sampai terbukti valid. Decode dengan ketat, normalisasi, validasi bentuk di handler, aturan bisnis di service, escape saat menampilkan. Endpoint register kita sekarang jauh lebih tahan banting menghadapi input liar.

    Backend kita makin lengkap, tapi ada satu masalah klasik yang belum tersentuh: frontend sering harus menebak bentuk request dan response API kita. Di bagian 28 kita bahas “Dokumentasi API dengan OpenAPI: Biar Frontend Tidak Menebak”.

    Kalau kamu sedang membangun aplikasi untuk bisnismu dan ingin backend yang aman menangani input user sejak hari pertama, tim kami bisa membantu lewat jasa pengembangan sistem aplikasi. Sampai jumpa di bagian berikutnya.

  • Belajar Linux dari Nol #5: Cara Copy dan Hapus File di Linux

    Belajar Linux dari Nol #5: Cara Copy dan Hapus File di Linux

    Cara copy file di Linux lewat terminal cukup satu perintah: cp namafile tujuan. Untuk memindahkan atau mengganti nama file pakai mv, dan untuk menghapus pakai rm. Tiga perintah itu, ditambah mkdir untuk membuat folder dan touch untuk membuat file kosong, sudah cukup untuk mengelola file sehari-hari tanpa menyentuh file manager sama sekali.

    Artikel ini bagian kelima dari seri Belajar Linux dari Nol. Di bagian ini kita praktik semua perintah pengelolaan file dan folder, termasuk hal yang sering bikin pemula celaka: kenapa rm -rf berbahaya dan kenapa file yang dihapus di terminal tidak masuk recycle bin.

    Prasyaratnya ringan. Kamu sudah bisa berpindah folder dengan cd dan melihat isi folder dengan ls. Kalau masih ragu soal posisi folder seperti /home atau /etc, baca dulu Belajar Linux dari Nol #4: Struktur Direktori Linux Lengkap. Semua contoh di sini dites di Ubuntu 24.04 dengan GNU coreutils 9.4, tapi perintahnya sama di hampir semua distro Linux.

    Persiapan: Membuat Folder dan File dengan mkdir dan touch

    Kita mulai dari area latihan yang aman di home directory. Buat satu folder khusus supaya kalau ada salah hapus, tidak ada file penting yang ikut hilang.

    cd ~
    mkdir latihan-file
    cd latihan-file

    mkdir singkatan dari make directory. Kalau mau membuat beberapa folder bertingkat sekaligus, tambahkan opsi -p:

    mkdir -p proyek/catatan/harian

    Tanpa -p, perintah di atas gagal karena folder proyek dan catatan belum ada. Dengan -p, semua folder di jalurnya dibuat sekaligus.

    Sekarang buat beberapa file kosong dengan touch:

    touch catatan.txt tugas.txt data.csv

    Cek hasilnya dengan ls:

    ls

    Output yang diharapkan:

    catatan.txt  data.csv  proyek  tugas.txt

    Fungsi asli touch sebenarnya memperbarui timestamp file. Tapi karena dia otomatis membuat file baru kalau filenya belum ada, perintah ini jadi cara tercepat membuat file kosong untuk latihan atau placeholder.

    Cara Copy File di Linux dengan Perintah cp

    Format dasarnya: cp sumber tujuan. Tujuan bisa berupa nama file baru atau nama folder.

    cp catatan.txt catatan-backup.txt
    cp tugas.txt proyek/

    Baris pertama menyalin catatan.txt menjadi file baru bernama catatan-backup.txt di folder yang sama. Baris kedua menyalin tugas.txt ke dalam folder proyek dengan nama yang sama. Perhatikan garis miring setelah proyek. Tidak wajib, tapi kebiasaan bagus karena menegaskan bahwa tujuannya folder, bukan nama file.

    Cek dengan ls proyek, harusnya muncul:

    catatan  tugas.txt

    Copy Folder Harus Pakai cp -r

    Kalau kamu coba menyalin folder dengan cp biasa, Linux menolak:

    cp proyek proyek-cadangan

    Output di Ubuntu 24.04:

    cp: -r not specified; omitting directory 'proyek'

    Di distro atau versi coreutils yang lebih lama, pesannya berbunyi cp: omitting directory 'proyek'. Maksudnya sama: folder dilewati, tidak disalin. Solusinya tambahkan opsi -r yang artinya recursive, alias salin folder beserta seluruh isinya sampai ke tingkat terdalam:

    cp -r proyek proyek-cadangan

    Sekarang ls akan menampilkan proyek-cadangan lengkap dengan semua isi di dalamnya.

    Hindari Menimpa File Tanpa Sadar: cp -i

    Ini jebakan yang jarang disadari pemula. Kalau file tujuan sudah ada, cp langsung menimpanya tanpa bertanya. Tidak ada peringatan, tidak ada konfirmasi, isi file lama hilang begitu saja. Supaya aman, biasakan pakai opsi -i (interactive):

    cp -i catatan.txt catatan-backup.txt

    Karena catatan-backup.txt sudah ada dari langkah sebelumnya, terminal akan bertanya dulu:

    cp: overwrite 'catatan-backup.txt'?

    Ketik y lalu Enter untuk menimpa, atau n untuk membatalkan. Opsi -i juga berlaku untuk mv dan rm.

    Memindahkan dan Rename File dengan mv

    Di Linux, memindahkan dan mengganti nama file itu satu perintah yang sama: mv. Logikanya, rename hanyalah memindahkan file ke jalur baru dengan nama berbeda.

    mv data.csv proyek/
    mv catatan.txt catatan-utama.txt

    Baris pertama memindahkan data.csv ke folder proyek. Baris kedua mengganti nama catatan.txt menjadi catatan-utama.txt. Kamu bahkan bisa melakukan keduanya sekaligus, pindah folder plus ganti nama dalam satu perintah:

    mv catatan-utama.txt proyek/catatan-final.txt

    File berpindah ke folder proyek dan namanya berubah menjadi catatan-final.txt. Berbeda dengan cp, perintah mv tidak butuh opsi -r untuk folder. mv proyek arsip langsung jalan karena yang dipindah hanya “alamat” foldernya, bukan isinya satu per satu.

    Menghapus File dan Folder: rm dan rmdir

    Menghapus file cukup dengan rm:

    rm catatan-backup.txt

    Tidak ada output kalau berhasil. Di Linux, diam berarti sukses. Cek saja dengan ls untuk memastikan filenya hilang.

    Untuk folder kosong, ada perintah khusus rmdir:

    mkdir folder-kosong
    rmdir folder-kosong

    rmdir hanya mau menghapus folder yang benar-benar kosong. Kalau masih ada isinya, dia menolak. Ini fitur pengaman, bukan bug.

    Menghapus Folder Beserta Isinya dengan rm -r

    Folder yang ada isinya dihapus dengan rm -r:

    rm -r proyek-cadangan

    Sama seperti di cp, opsi -r artinya recursive: hapus folder dan seluruh isinya sampai ke dasar. Kalau mau lebih hati-hati, gabungkan dengan -i supaya setiap file dikonfirmasi satu per satu sebelum dihapus.

    Kenapa rm -rf Berbahaya dan Tidak Ada Recycle Bin

    Kamu mungkin sering lihat perintah rm -rf di tutorial internet. Opsi -f artinya force: jangan tanya apa pun, jangan tampilkan error, hapus saja semuanya. Digabung -r, perintah ini menghapus folder apa pun beserta seluruh isinya tanpa satu pun konfirmasi.

    Masalahnya, terminal Linux tidak punya recycle bin. File yang dihapus lewat rm tidak pindah ke trash seperti di file manager Ubuntu atau Windows. Dia langsung lepas dari sistem file. Memulihkannya butuh tool forensik khusus, itu pun tidak dijamin berhasil, apalagi di SSD modern.

    Kesalahan klasiknya sepele: salah ketik path. rm -rf ~/latihan /file-lama dengan spasi nyasar sebelum garis miring akan dibaca sebagai dua target terpisah, dan salah satunya adalah folder di root sistem. Tim kami di Arrazy rutin bekerja di server klien untuk berbagai sistem aplikasi, dan aturan internalnya sederhana: sebelum menekan Enter pada perintah rm -rf, baca ulang path-nya minimal dua kali, dan kalau ragu jalankan ls dulu pada path yang sama untuk memastikan apa yang akan terhapus. Kebiasaan kecil ini jauh lebih murah daripada memulihkan data dari backup.

    Wildcard: Mengelola Banyak File Sekaligus dengan *.txt

    Wildcard * mewakili karakter apa pun dengan panjang berapa pun. Pola *.txt artinya semua file yang namanya berakhiran .txt. Ini yang membuat terminal jauh lebih cepat daripada file manager untuk operasi massal.

    touch laporan1.txt laporan2.txt laporan3.txt gambar.png
    ls *.txt

    Output yang diharapkan:

    laporan1.txt  laporan2.txt  laporan3.txt  tugas.txt

    File gambar.png tidak ikut karena tidak cocok dengan polanya. Wildcard bisa dipakai di hampir semua perintah file:

    mkdir arsip
    cp *.txt arsip/
    rm laporan*.txt

    Baris kedua menyalin semua file .txt ke folder arsip sekali jalan. Baris ketiga menghapus semua file yang namanya diawali laporan dan diakhiri .txt. Hati-hati: rm * tanpa pola apa pun menghapus semua file di folder aktif. Selalu cek dulu dengan ls memakai pola yang sama sebelum menjalankan rm dengan wildcard.

    Latihan Mini: Membangun dan Merapikan Struktur Folder Proyek

    Sekarang gabungkan semuanya. Skenarionya: kamu punya proyek tulisan blog yang filenya berantakan, lalu merapikannya seperti struktur proyek sungguhan.

    1. Buat struktur foldernya sekali jalan:
      cd ~/latihan-file
      mkdir -p blog/{draf,publish,aset}

      Kurung kurawal di situ adalah brace expansion, cara cepat membuat tiga subfolder sekaligus: draf, publish, dan aset.

    2. Buat file draf dan aset dummy:
      cd blog
      touch draf-linux.txt draf-golang.txt draf-laravel.txt logo.png banner.png
    3. Pindahkan semua draf ke foldernya dengan wildcard:
      mv draf-*.txt draf/
      mv *.png aset/
    4. Anggap draf Linux sudah selesai. Pindahkan ke publish sambil ganti nama:
      mv draf/draf-linux.txt publish/artikel-linux.txt
    5. Buat backup seluruh folder blog:
      cd ~/latihan-file
      cp -r blog blog-backup
    6. Verifikasi hasil akhirnya:
      ls blog/draf blog/publish blog/aset

      Output yang diharapkan:

      blog/aset:
      banner.png  logo.png
      
      blog/draf:
      draf-golang.txt  draf-laravel.txt
      
      blog/publish:
      artikel-linux.txt
    7. Terakhir, bersihkan backup yang sudah tidak dipakai:
      rm -r blog-backup

    Kalau semua langkah jalan mulus, kamu sudah menguasai siklus lengkap pengelolaan file: buat, salin, pindah, rename, dan hapus.

    Error yang Sering Dialami Pemula

    cp: -r not specified; omitting directory

    Muncul saat menyalin folder tanpa opsi -r. Di coreutils lama pesannya cp: omitting directory. Penyebabnya sama: cp tidak mau menyalin folder kecuali diminta eksplisit. Solusi: tambahkan -r, misalnya cp -r proyek backup-proyek.

    rm: cannot remove ‘folder’: Is a directory

    Muncul saat menghapus folder dengan rm polos. rm tanpa opsi hanya bekerja pada file. Solusi: pakai rm -r namafolder untuk folder beserta isinya, atau rmdir namafolder kalau foldernya kosong.

    rmdir: failed to remove ‘folder’: Directory not empty

    Muncul saat rmdir dipakai pada folder yang masih ada isinya, termasuk file tersembunyi yang tidak terlihat di ls biasa. Cek dulu dengan ls -a namafolder. Kalau isinya memang mau dibuang semua, pakai rm -r namafolder.

    File Tertimpa Tanpa Peringatan

    Ini bukan pesan error, justru itu bahayanya: tidak ada pesan apa pun. cp dan mv menimpa file tujuan yang sudah ada secara diam-diam. Kalau kamu baru sadar setelah kejadian, isi file lama sudah tidak bisa dikembalikan. Pencegahannya: biasakan cp -i dan mv -i supaya selalu ada konfirmasi sebelum menimpa. Beberapa orang bahkan membuat alias permanen untuk ini, yang akan kita bahas saat masuk materi shell script.

    Lanjut ke Bagian Berikutnya

    Kamu sekarang bisa membuat, menyalin, memindahkan, dan menghapus file dengan aman, termasuk operasi massal pakai wildcard. Satu keterampilan yang belum kita sentuh: mengubah isi file langsung dari terminal. Itu materi bagian berikutnya, “Belajar Linux dari Nol #6: Cara Edit File di Terminal (Nano)”, yang terbit menyusul. Pantau daftar lengkapnya di halaman hub seri Belajar Linux.

    Referensi

  • Cara Mengelola Reseller dan Dropshipper Biar Untung, Bukan Repot

    Cara Mengelola Reseller dan Dropshipper Biar Untung, Bukan Repot

    Punya reseller itu enak di awal. Ada orang lain yang ikut jualan, omzet naik tanpa perlu buka cabang atau pasang iklan sendiri. Masalahnya baru terasa waktu jumlahnya bertambah. Chat order masuk dari mana-mana, ada yang banting harga sampai reseller lain protes, ada yang minta retur padahal barangnya rusak karena salah simpan. Niatnya cari untung, yang datang malah repot.

    Jawaban singkatnya begini. Reseller menguntungkan kalau tiga hal beres. Pertama, aturan main tertulis soal harga, wilayah, dan retur. Kedua, sistem harga bertingkat yang masuk akal untuk semua lapis. Ketiga, alur order dan stok yang tidak dikerjakan manual satu per satu. Tiga hal itu yang membedakan supplier yang untung dengan supplier yang cuma capek. Artikel ini membahas cara membereskan ketiganya satu per satu.

    Reseller dan Dropshipper Itu Beda, Perlakuannya Juga Harus Beda

    Dua istilah ini sering dicampur, padahal cara kerjanya berbeda. Reseller membeli stok dari kamu, menyimpannya sendiri, lalu menjual ke pembelinya. Dropshipper tidak pegang stok sama sekali. Dia terima order dari pembeli, meneruskan ke kamu, lalu kamu yang mengirim barang atas nama toko dia.

    Konsekuensi operasionalnya beda jauh. Reseller menanggung risiko stok. Kalau barang tidak laku, itu masalah dia. Urusan kamu selesai begitu barang sampai di tangannya. Dropshipper sebaliknya. Setiap order berarti kamu yang packing, kamu yang kirim satuan, dan nama toko dia yang tertera di paket. Salah tempel alamat sedikit saja, kamu yang repot menanganinya dan dia yang kena komplain pembeli.

    Karena risiko dan beban kerjanya beda, harga dan aturannya sebaiknya juga dibedakan. Dropshipper wajar dapat harga sedikit di atas harga reseller, karena kamu yang menanggung kerja kirim per order. Reseller dapat harga lebih murah karena dia berani ambil stok di depan.

    Tulis Aturan Main Sejak Reseller Pertama

    Kesalahan paling umum: aturan baru dibuat setelah ada masalah. Padahal aturan yang ditulis belakangan selalu terasa seperti hukuman. Kalau ditulis sejak awal, semua orang masuk dengan ekspektasi yang sama. Minimal empat hal ini harus tertulis, cukup satu halaman saja.

    • Harga jual minimum. Ini aturan paling penting. Tanpa batas bawah, reseller akan saling banting harga demi cepat laku. Ujungnya pasar rusak, margin semua orang tergerus, dan produk kamu terkesan murahan. Tetapkan harga jual minimum, tulis sanksinya, dan tegakkan.
    • Kebijakan retur barang rusak. Perjelas mana yang ditanggung kamu dan mana yang tidak. Misalnya, cacat produksi atau rusak saat pengiriman pertama bisa ditukar dengan bukti video unboxing, tapi barang yang rusak karena salah penyimpanan di gudang reseller bukan tanggungan kamu. Beri batas waktu klaim yang jelas, misalnya tiga hari setelah barang diterima.
    • Siapa menanggung ongkir. Ongkir kirim stok ke reseller, ongkir order dropship, dan ongkir retur itu tiga hal berbeda. Tentukan sejak awal siapa membayar yang mana, supaya tidak ada tawar-menawar di tengah jalan.
    • Larangan klaim berlebihan. Reseller yang semangat kadang kebablasan saat promosi. Produk perawatan kulit dibilang menyembuhkan, suplemen dibilang bikin kurus dalam seminggu. Klaim seperti ini bisa jadi masalah hukum, dan yang kena nama brand kamu. Tulis batasan klaim yang boleh dan tidak boleh dipakai.

    Kalau wilayah pemasaran juga sensitif di bisnis kamu, misalnya ada agen daerah yang minta eksklusivitas, atur juga pembagian wilayahnya di dokumen yang sama.

    Struktur Harga Bertingkat yang Masuk Akal

    Harga bertingkat itu sederhana konsepnya: makin banyak ambil, makin murah harganya. Yang sering keliru adalah jarak antar tingkat. Terlalu tipis, tidak ada insentif naik level. Terlalu lebar, margin kamu sendiri yang habis.

    Contoh sederhana untuk produk dengan harga ritel Rp100.000:

    • Harga ritel: Rp100.000, untuk pembeli akhir
    • Harga reseller: Rp80.000, minimal ambil 5 pcs
    • Harga agen: Rp70.000, minimal ambil 50 pcs

    Perhatikan marginnya masuk untuk semua lapis. Reseller yang jual di harga ritel untung Rp20.000 per pcs. Agen bisa memasok reseller di bawahnya dengan harga Rp80.000 dan masih untung Rp10.000 per pcs tanpa repot jual satuan. Kamu sendiri tetap punya margin sehat di harga Rp70.000. Semua orang punya alasan untuk tetap main di sistem ini.

    Dua rambu tambahan. Pertama, jangan bikin level terlalu banyak. Tiga tingkat biasanya cukup. Lima tingkat ke atas bikin selisih antar level jadi tipis dan sulit dikontrol. Kedua, harga tiap level harus tetap di atas harga jual minimum yang kamu tetapkan, supaya tidak ada celah perang harga yang legal.

    Seleksi Reseller, Jangan Asal Terima

    Banyak supplier bangga dengan jumlah reseller. Punya 500 reseller terdengar keren, tapi kalau yang aktif order cuma 30, sisanya cuma nama di daftar. Lebih buruk lagi, reseller pasif yang pernah dapat harga khusus bisa sewaktu-waktu jual sisa stoknya di harga asal laku, dan itu merusak pasar reseller yang aktif.

    Filter paling sederhana adalah minimal order pertama. Orang yang berani ambil 5 atau 10 pcs di awal jelas lebih serius daripada yang cuma minta daftar harga lalu hilang. Angka minimalnya tidak perlu besar, yang penting cukup untuk menyaring yang iseng.

    Satu lagi, jangan obral gelar agen. Status agen dengan harga termurah harus diperoleh lewat volume yang terbukti, bukan lewat nego di chat pertama. Kalau semua orang bisa jadi agen hanya dengan meminta, struktur harga bertingkat yang kamu susun tadi runtuh sendiri. Pegang prinsip ini: sedikit reseller yang aktif jauh lebih menguntungkan daripada banyak reseller yang pasif.

    Empat Kerepotan Paling Umum dan Cara Membereskannya

    Aturan dan harga sudah rapi, sekarang bagian yang paling menyita waktu: operasional harian. Empat hal ini yang paling sering bikin supplier kewalahan.

    Rekap order dari puluhan chat

    Order masuk lewat WhatsApp dengan format bebas. Ada yang kirim list, ada yang voice note, ada yang cuma foto produk plus tulisan “ini 3”. Setiap malam kamu rekap manual ke Excel, dan selalu ada yang terlewat. Solusinya, pindahkan order ke satu pintu. Bisa lewat form order sederhana, bisa juga lewat katalog khusus reseller yang harganya sudah sesuai level mereka. Soal memilih format katalog yang pas, kami pernah membahasnya di artikel katalog produk digital: PDF, web, atau WhatsApp.

    Pertanyaan stok yang itu-itu saja

    “Kak, warna hitam ready?” masuk dua puluh kali sehari dari orang berbeda. Jawabnya sebentar, tapi kali dua puluh jadi satu jam sendiri. Cara murahnya, kirim broadcast update stok ke grup reseller secara berkala, misalnya tiap pagi. Cara yang lebih rapi, sediakan halaman stok yang bisa dicek reseller kapan saja tanpa perlu bertanya.

    Bukti transfer nyangkut

    Reseller bilang sudah transfer, bukti terselip di antara ratusan chat, barang telanjur dikirim padahal dana belum masuk. Kalau volume sudah lumayan, pembayaran otomatis lewat payment gateway atau VA menyelesaikan ini. Dana terverifikasi sendiri, tidak ada lagi drama cek mutasi satu per satu.

    Alamat dropship salah ketik

    Ini kerepotan khas dropship. Alamat dikirim lewat chat bebas, kamu salin manual ke resi, satu angka rumah keliru, paket nyasar, dan semua pihak saling menyalahkan. Aturannya sederhana: data penerima wajib lewat form, bukan chat. Apa yang diketik dropshipper, itu yang tercetak di resi. Salah ketik jadi tanggung jawab dia, bukan kamu.

    Bikin Reseller Betah, dan Tahu Kapan Harus Naik Level

    Reseller yang betah itu bukan yang dapat harga paling murah, tapi yang merasa dibantu jualan. Tiga hal ini efeknya besar padahal modalnya kecil. Pertama, materi promosi siap pakai: foto produk yang bagus, copy caption yang tinggal salin, video singkat kalau ada. Reseller yang tidak perlu bikin konten sendiri akan lebih rajin posting. Kedua, info produk baru diberikan ke reseller lebih dulu sebelum diumumkan ke publik, supaya mereka merasa jadi orang dalam. Ketiga, grup khusus reseller yang isinya berguna: update stok, tips jualan, jawaban cepat. Bukan grup yang isinya cuma promosi dari kamu sendiri.

    Terakhir, kenali tanda kapan cara manual sudah tidak layak dipertahankan. Kalau order sudah puluhan per hari, rekap harga per level masih dihitung tangan, dan komisi agen dihitung akhir bulan sambil menahan pusing, itu tanda kamu butuh sistem reseller sendiri. Bentuknya portal order khusus reseller, harga yang otomatis menyesuaikan level akun, stok yang kelihatan real time, dan rekap komisi yang terhitung sendiri. Sistem seperti ini bisa dibuat sesuai alur bisnis kamu lewat jasa pembuatan sistem aplikasi, jadi tidak perlu memaksakan alur ke aplikasi jadi yang belum tentu cocok.

    Intinya, reseller itu aset kalau dikelola, dan sumber pusing kalau dibiarkan tumbuh liar. Mulai dari yang paling murah dulu: tulis aturan mainnya, rapikan struktur harganya, lalu pindahkan order ke satu pintu. Sisanya biasanya ikut beres sendiri.

  • Aplikasi Booking Barbershop dan Salon: Antrian sampai Komisi

    Aplikasi Booking Barbershop dan Salon: Antrian sampai Komisi

    Aplikasi booking barbershop dan salon pada dasarnya menyelesaikan tiga masalah: antrian yang semrawut, hitungan komisi kapster yang bikin tegang tiap akhir bulan, dan pelanggan yang datang sekali lalu tidak pernah balik. Kalau tiga masalah itu belum terasa di tempat Anda, kemungkinan besar Anda belum butuh aplikasi. Kalau salah satunya sudah rutin bikin pusing, artikel ini untuk Anda.

    Yang perlu digarisbawahi, kebutuhan barbershop dan salon berbeda dengan booking pada umumnya. Pelanggan tidak sekadar memesan jam. Mereka memesan orang. Antrian juga tidak pernah rapi karena walk-in tetap jalan di samping yang sudah booking. Di bawah ini kita bahas satu per satu fitur yang benar-benar relevan, kapan buku tulis sebenarnya masih cukup, dan pertimbangan singkat antara aplikasi jadi dengan aplikasi custom.

    Booking per kapster, bukan sekadar pilih jam

    Pelanggan setia barbershop biasanya loyal ke orangnya, bukan ke tempatnya. Ada yang hanya mau dipotong satu kapster tertentu karena sudah hafal modelnya. Di salon juga sama. Pelanggan creambath atau smoothing sering punya stylist andalan yang tahu kondisi rambutnya.

    Karena itu sistem booking untuk industri ini wajib bisa dua hal. Pertama, pelanggan bisa memilih kapster atau stylist tertentu, lengkap dengan jadwal kosongnya. Kedua, ada opsi “siapa saja yang kosong” untuk pelanggan yang tidak pilih orang dan hanya ingin cepat dilayani. Dua jalur ini harus jalan bersamaan tanpa saling tabrak.

    Dari sisi operasional, ini juga berarti jadwal dikelola per kapster. Kalau ada kapster izin atau cuti, slot miliknya otomatis tertutup dan pelanggan lamanya bisa ditawari pindah ke orang lain atau ganti hari. Hal kecil seperti ini yang sering bikin booking manual lewat WhatsApp berantakan, karena admin harus mengingat jadwal tiap orang di kepala.

    Walk-in dan booking jalan bareng tanpa drama

    Barbershop dan salon hampir tidak mungkin seratus persen booking. Selalu ada yang datang langsung. Masalah muncul ketika orang yang sudah booking datang tepat waktu, tapi kursinya masih dipakai walk-in yang datang lebih dulu. Dua pelanggan kecewa sekaligus.

    Sistem yang baik menggabungkan dua alur ini dalam satu antrian. Prinsipnya sederhana. Yang booking dapat prioritas di jam yang dia pesan. Walk-in masuk antrian berjalan dan mengisi celah di antara slot booking. Sistem yang menghitung urutannya, bukan admin.

    Efek yang paling terasa ada di estimasi tunggu. Pelanggan walk-in bisa lihat perkiraan berapa lama lagi gilirannya, entah lewat layar di ruang tunggu atau dari HP masing-masing. Mereka jadi bisa keluar dulu cari makan atau ngopi, lalu balik mendekati giliran. Ruang tunggu tidak penuh, dan pelanggan tidak kabur ke tempat sebelah hanya karena antrian kelihatan panjang.

    Komisi kapster dan laporan pemilik: semua angka kelihatan

    Ini bagian yang paling sering memicu gesekan di dalam tim, dan justru paling jarang dibahas ketika orang bicara aplikasi booking.

    Komisi dihitung sistem, bukan di kertas

    Skema komisi barbershop dan salon jarang seragam. Potong rambut mungkin komisinya 40 persen, creambath 30 persen, sementara treatment tertentu punya angka sendiri. Ada juga yang membedakan komisi kapster senior dan junior. Kalau semua ini dicatat manual, akhir bulan hampir pasti ada selisih. Kapster merasa jumlah kepalanya lebih banyak dari catatan, pemilik merasa catatannya sudah benar. Ribut.

    Dengan sistem, setiap transaksi yang selesai otomatis tercatat atas nama kapster yang mengerjakan, lengkap dengan jenis layanan dan persentase komisinya. Rekap per minggu atau per bulan tinggal dibuka, tidak perlu dihitung ulang. Yang tidak kalah penting, kapster bisa melihat rekapnya sendiri kapan saja. Transparansi ini yang membuat hitungan komisi berhenti jadi bahan curiga. Angkanya sama di mata semua orang.

    Laporan yang layak dilihat pemilik tiap akhir bulan

    Data yang sama juga menjawab pertanyaan yang selama ini dijawab pakai perasaan.

    • Layanan mana yang paling laku, dan mana yang sebenarnya sepi peminat
    • Jam dan hari paling ramai, sebagai dasar mengatur shift kapster
    • Performa tiap kapster, dari jumlah pelanggan sampai nilai transaksi
    • Produk ritel yang terjual, misalnya pomade atau vitamin rambut, supaya stok tidak menumpuk di barang yang tidak jalan

    Laporan seperti ini yang membedakan pemilik yang mengelola bisnis dengan pemilik yang hanya menjaga toko.

    Membership dan retensi: membuat pelanggan balik lagi

    Biaya mendapatkan pelanggan baru selalu lebih mahal daripada membuat pelanggan lama datang lagi. Di industri ini, ada tiga fitur yang bekerja langsung ke retensi.

    Paket dan membership dengan kuota

    Salon banyak yang menjual paket, misalnya creambath 10 kali dengan harga lebih murah. Masalah klasiknya ada di pencatatan. Kartu member hilang, stempel lupa dicap, atau pelanggan dan kasir beda hitungan sisa kuota. Di sistem, setiap kedatangan otomatis memotong kuota, sisa kuota terlihat jelas, dan masa berlaku paket terpantau. Menjelang kuota habis atau masa berlaku hampir lewat, sistem bisa mengirim pengingat supaya pelanggan memakai haknya, sekaligus membuka peluang perpanjangan paket.

    Riwayat pelanggan yang tersimpan rapi

    Kapster yang hafal pelanggannya adalah aset. Masalahnya, hafalan tidak bisa dioper. Kalau kapster andalan resign, hafalannya ikut pergi. Riwayat pelanggan di sistem menyimpan hal seperti model potongan terakhir, catatan preferensi semisal tidak mau dipakaikan razor atau alergi produk tertentu, sampai produk yang cocok untuk rambutnya. Kapster mana pun yang menangani jadi punya bekal, dan pelanggan merasa dikenali walau dilayani orang baru.

    Reminder otomatis di dua momen

    Reminder pertama sifatnya operasional, yaitu pengingat H-1 sebelum jadwal booking. Ini menekan angka pelanggan yang lupa datang dan slot yang hangus sia-sia. Reminder kedua sifatnya retensi. Rata-rata orang potong rambut tiap tiga sampai empat minggu. Sistem bisa mengirim pesan halus seperti “sudah tiga minggu sejak potong terakhir, mau amankan jadwal minggu ini” ke pelanggan yang sudah lewat siklusnya. Pesan sederhana ini sering cukup untuk memindahkan pelanggan dari “nanti saja” jadi booking.

    Kapan buku tulis masih cukup

    Supaya adil, tidak semua barbershop dan salon butuh aplikasi hari ini. Kalau kondisi Anda seperti ini, buku tulis dan WhatsApp masih sangat bisa jalan.

    • Satu sampai dua kapster, dan salah satunya pemilik sendiri
    • Hampir semua pelanggan walk-in dan antrian jarang lebih dari dua tiga orang
    • Tidak ada skema komisi, atau komisinya rata dan gampang dihitung
    • Belum ada paket atau membership yang perlu dilacak kuotanya

    Aplikasi mulai masuk akal ketika gejalanya muncul, misalnya double booking mulai sering, admin kewalahan membalas chat booking, rekap komisi tiap akhir bulan memakan waktu berjam jam dan tetap ada yang protes, atau Anda mulai buka cabang kedua dan tidak bisa lagi mengawasi semuanya sendiri.

    Aplikasi jadi atau bikin custom

    Kalau sudah yakin butuh, ada dua jalan. Aplikasi jadi berbasis langganan cocok untuk yang alurnya standar dan ingin mulai cepat dengan biaya bulanan. Kekurangannya, Anda mengikuti alur aplikasi, bukan sebaliknya, dan skema komisi atau membership yang agak unik sering tidak terakomodasi.

    Aplikasi custom cocok kalau Anda punya alur sendiri yang sudah terbukti jalan, skema komisi berlapis, beberapa cabang yang datanya ingin dipantau terpusat, atau ingin data pelanggan sepenuhnya jadi milik sendiri. Investasi awalnya lebih besar, tapi tidak ada biaya langganan per bulan yang terus berjalan dan fiturnya mengikuti cara kerja Anda. Gambaran umum cara kami membangun sistem seperti ini bisa dibaca di halaman pengembangan sistem aplikasi. Kalau Anda ingin pelanggan booking dari HP lewat aplikasi Android atau iOS, alurnya mirip dengan layanan pembuatan aplikasi mobile kami.

    Pertanyaan yang sering muncul

    Apakah pelanggan harus install aplikasi untuk bisa booking?

    Tidak harus. Untuk tahap awal, booking lewat halaman web yang dibuka dari link di Instagram atau WhatsApp biasanya sudah cukup, tanpa perlu install apa pun. Aplikasi mobile baru layak dipertimbangkan kalau pelanggan tetap Anda sudah banyak dan Anda ingin fitur seperti notifikasi langsung ke HP mereka.

    Berapa biaya membangun aplikasi booking custom untuk barbershop atau salon?

    Tergantung cakupan fiturnya. Sistem booking dan antrian sederhana tentu jauh lebih ringan daripada sistem lengkap dengan komisi, membership, multi cabang, dan aplikasi mobile. Umumnya proyek custom seperti ini mulai dari belasan hingga puluhan juta rupiah. Cara paling akurat adalah mendiskusikan alur bisnis Anda dulu, baru dihitung dari situ.

    Berapa lama pengerjaannya?

    Untuk versi pertama dengan fitur inti seperti booking per kapster, antrian, dan rekap komisi, umumnya butuh hitungan satu sampai tiga bulan. Fitur lanjutan seperti membership dan laporan mendalam bisa menyusul bertahap setelah sistem intinya jalan dan terbukti dipakai.

    Langkah paling masuk akal bukan langsung minta penawaran, tapi memetakan dulu masalah mana yang paling mahal di tempat Anda. Antrian yang bikin pelanggan kabur, komisi yang bikin kapster tidak betah, atau pelanggan lama yang diam diam hilang. Dari situ baru terlihat fitur mana yang layak dibangun duluan.

    Kalau mau mendiskusikannya, ceritakan saja kondisi barbershop atau salon Anda lewat halaman kontak kami. Kami bantu petakan kebutuhannya dulu, termasuk kalau kesimpulannya ternyata Anda belum perlu aplikasi sekarang.