Aplikasi Kasir VB.NET: Membuat Kasir Toko dengan Cetak Struk dari Nol

Aplikasi kasir VB.NET dengan layar transaksi, kembalian otomatis, dan cetak struk 58 mm

Bayangkan warung kelontong di ujung gang pada jam pulang kerja. Antrean pembeli memanjang, sementara pemilik warung masih menjumlah harga di kalkulator lalu menulis nota dengan tangan. Akibatnya, kembalian kadang keliru dan stok mie instan tiba-tiba habis tanpa ada yang sadar. Karena itu, pada tutorial ini kita membangun aplikasi kasir VB.NET yang sederhana, ringan, dan siap mencetak struk ke printer thermal 58 mm.

Aplikasi ini bernama SC Kasir. Dasarnya Windows Forms di .NET 8, tanpa paket NuGet tambahan, dan tanpa server database. Semua data tersimpan sebagai file JSON di samping file .exe, sehingga aplikasi bisa langsung jalan di komputer kasir mana pun. Selain itu, seluruh source code bersifat MIT, jadi Anda bebas mempelajari, mengubah, bahkan menjualnya kembali.

Aplikasi kasir VB.NET: layar transaksi dengan keranjang, total belanja, dan kembalian
Hasil akhir: layar kasir SC Kasir dengan keranjang belanja, total, uang bayar, dan kembalian otomatis.

Jika ingin langsung mencoba, unduh source code lengkapnya secara gratis di SC Kasir VB.NET di sharecode.id. Setelah itu, ikuti artikel ini sambil membuka project-nya agar setiap bagian kode terasa lebih jelas.

Fitur Aplikasi Kasir yang Akan Kita Bangun

Sebelum menulis kode, mari sepakati dulu fitur yang akan kita kerjakan. Dengan begitu, setiap langkah nanti punya tujuan yang jelas.

  • Dasbor berisi penjualan hari ini, jumlah transaksi, jumlah jenis barang, dan daftar stok menipis.
  • Data barang dengan kode otomatis BRG001, BRG002, dan seterusnya, lengkap dengan fitur cari, ubah, dan hapus.
  • Transaksi memakai keranjang di DataGridView, cek stok, serta hitung total dan kembalian secara otomatis.
  • Nomor nota harian berformat TRX-20261108-001 yang kembali ke 001 setiap hari.
  • Cetak struk 58 mm lewat PrintDocument dan Print Preview.
  • Riwayat penjualan per rentang tanggal, detail barang, cetak ulang struk, serta ekspor CSV.
  • Pengaturan toko untuk nama, alamat, telepon, dan pesan di kaki struk.

Persiapan Membuat Aplikasi Kasir VB.NET

Perangkat yang Perlu Anda Siapkan

Pertama, pasang Visual Studio 2022 versi 17.8 atau lebih baru dengan workload .NET desktop development. Edisi Community sudah cukup dan gratis. Selanjutnya, pastikan .NET 8 SDK ikut terpasang, karena workload tersebut biasanya sudah membawanya. Terakhir, siapkan Windows 10 atau 11. Visual Studio 2019 tidak bisa membuka project .NET 8, jadi jangan memakai versi lama.

Membuat Project WinForms VB dari Nol

  1. Buka Visual Studio, lalu pilih Create a new project.
  2. Ketik “Windows Forms App” pada kolom pencarian, kemudian pilih template berlogo VB (bukan C#).
  3. Isi nama project SCKasir, tentukan folder, lalu klik Next.
  4. Pada pilihan framework, ambil .NET 8.0 (Long Term Support) dan klik Create.
  5. Hapus Form1.vb bawaan, karena nanti kita memakai Sub Main sendiri.
  6. Buat folder Forms lewat klik kanan project, Add, New Folder.

Mengatur File SCKasir.vbproj

Klik dua kali nama project di Solution Explorer untuk membuka file vbproj, lalu samakan isinya seperti berikut.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <RootNamespace>SCKasir</RootNamespace>
    <StartupObject>SCKasir.Program</StartupObject>
    <MyType>WindowsFormsWithCustomSubMain</MyType>

    <OptionExplicit>On</OptionExplicit>
    <OptionStrict>On</OptionStrict>
    <OptionInfer>On</OptionInfer>
  </PropertyGroup>
  <!-- ... -->
</Project>

Bagian StartupObject dan MyType membuat aplikasi mulai dari module Program, bukan dari form. Sementara itu, OptionStrict On memaksa kita menulis konversi tipe secara eksplisit. Memang terasa lebih cerewet, namun banyak bug angka dan teks langsung tertangkap saat build, bukan saat kasir sedang melayani pembeli.

Struktur Folder dan File

SCKasir.sln
SCKasir/
├── SCKasir.vbproj
├── Program.vb
├── Model.vb
├── Penyimpanan.vb
├── Repo.vb
├── Util.vb
├── Struk.vb
└── Forms/
    ├── FormUtama.vb       (+ .Designer.vb)
    ├── FormBarang.vb
    ├── FormTransaksi.vb
    ├── FormRiwayat.vb
    └── FormPengaturan.vb
FileFungsi
Program.vbTitik masuk aplikasi, budaya id-ID, penangkap error
Model.vbClass Barang, ItemTransaksi, Transaksi, Pengaturan
Penyimpanan.vbBaca dan tulis file JSON di folder data
Repo.vbData aktif, pencarian, kode barang dan nomor nota otomatis
Util.vbFormat Rupiah, input nominal, gaya grid, kotak pesan
Struk.vbSusun teks struk dan cetak lewat PrintDocument
Forms\*.vbLima layar: dasbor, barang, transaksi, riwayat, pengaturan

Arsitektur Aplikasi: Form, Repo, Penyimpanan, File JSON

Diagram arsitektur aplikasi kasir VB.NET empat lapisan: Form, Repo, Penyimpanan, file JSON
Empat lapisan SC Kasir. Form hanya berbicara dengan Repo, lalu Repo memakai modul Penyimpanan untuk menulis JSON.

Arsitektur ini sengaja tipis. Lapisan paling atas berisi form yang hanya mengurus tampilan dan event. Kemudian, module Repo memegang semua list data selama aplikasi berjalan, termasuk logika nomor otomatis. Di bawahnya, module Penyimpanan menerjemahkan list tersebut menjadi teks JSON. Terakhir, tiga file di folder data menjadi tempat data bermukim.

Keuntungannya cukup besar. Misalnya, jika suatu saat Anda ingin pindah ke SQLite, cukup ganti isi Penyimpanan dan Repo tanpa menyentuh form sama sekali.

Langkah 1: Membuat Model Data

Class Barang

Tambahkan file Model.vb, lalu tulis class pertama berikut.

Imports System.Text.Json.Serialization

Public Class Barang
    Public Property Kode As String = ""
    Public Property Nama As String = ""
    Public Property Satuan As String = "pcs"
    Public Property Harga As Decimal
    Public Property Stok As Integer

    ''' <summary>Teks yang tampil di ComboBox transaksi.</summary>
    <JsonIgnore>
    Public ReadOnly Property Tampilan As String
        Get
            Return Kode & " - " & Nama
        End Get
    End Property
End Class

Harga memakai tipe Decimal, bukan Double, agar perhitungan uang tidak menghasilkan pecahan aneh seperti 0,30000000004. Selain itu, properti Tampilan menggabungkan kode dan nama untuk ComboBox. Atribut <JsonIgnore> mencegah properti itu ikut masuk ke file, karena nilainya selalu bisa kita susun ulang.

Class ItemTransaksi dan Transaksi

Public Class ItemTransaksi
    Public Property Kode As String = ""
    Public Property Nama As String = ""
    Public Property Harga As Decimal
    Public Property Qty As Integer

    <JsonIgnore>
    Public ReadOnly Property Subtotal As Decimal
        Get
            Return Harga * Qty
        End Get
    End Property
End Class

Perhatikan bahwa item transaksi menyimpan Nama dan Harga sendiri, bukan sekadar kode barang. Alasannya sederhana: jika besok harga gula naik, struk lama tetap menunjukkan harga saat pembeli membayar. Selanjutnya, Subtotal menjadi properti hitung yang tidak ikut tersimpan.

Public Class Transaksi
    Public Property NoNota As String = ""
    Public Property Tanggal As DateTime
    Public Property Items As New List(Of ItemTransaksi)()
    Public Property Bayar As Decimal

    <JsonIgnore>
    Public ReadOnly Property Total As Decimal
        Get
            Return Items.Sum(Function(i) i.Subtotal)
        End Get
    End Property

    ' ... Kembali = Bayar - Total, JumlahItem = Items.Sum(Qty)
End Class

Class Transaksi hanya menyimpan empat data inti. Sebaliknya, total, kembalian, dan jumlah item muncul dari rumus LINQ. Dengan demikian, angka di file JSON tidak mungkin bertentangan dengan isi item.

Class Pengaturan dan Bentuk File JSON

Public Class Pengaturan
    Public Property NamaToko As String = "Toko Sharecode Mandiri"
    Public Property Alamat As String = "Jl. Merdeka No. 1"
    Public Property Telepon As String = "0812-0000-0000"
    Public Property PesanStruk As String = "Terima kasih atas kunjungan Anda"
End Class

Nilai bawaan pada class ini langsung muncul di struk pertama, jadi aplikasi tetap rapi walau pemilik toko belum membuka menu pengaturan. Gambar berikut memperlihatkan hubungan class dengan isi file.

Perbandingan class Transaksi VB.NET dengan isi file transaksi.json
Properti biasa masuk ke transaksi.json, sedangkan properti JsonIgnore (merah) muncul dari rumus. Offset zona waktu mengikuti pengaturan Windows.

Langkah 2: Menyimpan Data ke File JSON

Menentukan Folder Data dan Membaca File

Buat file Penyimpanan.vb. Module ini memakai System.Text.Json yang sudah tersedia di .NET 8.

Public Module Penyimpanan
    Private ReadOnly Opsi As New JsonSerializerOptions With {.WriteIndented = True}

    ' Folder "data" di samping file .exe
    Public ReadOnly Property FolderData As String
        Get
            Dim folder = Path.Combine(AppContext.BaseDirectory, "data")
            Directory.CreateDirectory(folder)
            Return folder
        End Get
    End Property

    ' Baca file JSON menjadi objek bertipe T
    Public Function Muat(Of T As {Class, New})(namaFile As String) As T
        Dim lokasi = Path.Combine(FolderData, namaFile)
        If Not File.Exists(lokasi) Then Return New T()
        Try
            Dim hasil = JsonSerializer.Deserialize(Of T)(File.ReadAllText(lokasi), Opsi)
            Return If(hasil, New T())
        Catch ex As JsonException
            ' File rusak: simpan salinannya agar data lama tidak hilang
            File.Copy(lokasi, lokasi & ".rusak-" & DateTime.Now.ToString("yyyyMMddHHmmss"), True)
            ' ... tampilkan pesan, lalu pakai data kosong
            Return New T()
        End Try
    End Function

Properti FolderData selalu menunjuk ke folder data di samping exe dan membuatnya bila belum ada. Kemudian, fungsi generik Muat(Of T) bisa membaca list barang, list transaksi, maupun objek pengaturan dengan satu kode yang sama. Jika file rusak, aplikasi tidak langsung tertutup. Sebaliknya, aplikasi menyalin file itu dengan akhiran .rusak- lalu mulai dengan data kosong.

Menulis File dengan Aman

    Public Sub Simpan(Of T)(namaFile As String, data As T)
        Dim lokasi = Path.Combine(FolderData, namaFile)
        Dim sementara = lokasi & ".tmp"
        ' Tulis ke file sementara dulu, baru ganti file asli (aman bila listrik mati)
        File.WriteAllText(sementara, JsonSerializer.Serialize(data, Opsi))
        File.Move(sementara, lokasi, True)
    End Sub
End Module

Trik file sementara ini penting untuk toko kecil yang listriknya sering padam. Jika komputer mati tepat saat menulis, yang rusak hanya file .tmp, sementara file asli masih utuh. Setelah penulisan selesai, File.Move dengan parameter True menimpa file lama dalam satu langkah.

Langkah 3: Repo, Kode Barang, dan Nomor Nota Otomatis

Memuat Semua Data saat Aplikasi Mulai

Public Module Repo
    Public Const BATAS_STOK_MENIPIS As Integer = 5

    Private Const FILE_BARANG As String = "barang.json"
    Private Const FILE_TRANSAKSI As String = "transaksi.json"
    Private Const FILE_PENGATURAN As String = "pengaturan.json"

    Public DaftarBarang As New List(Of Barang)()
    Public DaftarTransaksi As New List(Of Transaksi)()
    Public Toko As New Pengaturan()

    Public Sub MuatSemua()
        Dim pertamaKali = Not AdaFile(FILE_BARANG)

        DaftarBarang = Muat(Of List(Of Barang))(FILE_BARANG)
        DaftarTransaksi = Muat(Of List(Of Transaksi))(FILE_TRANSAKSI)
        Toko = Muat(Of Pengaturan)(FILE_PENGATURAN)

        If pertamaKali Then
            IsiDataContoh()
            SimpanBarang()
            SimpanPengaturan()
        End If
    End Sub

Module Repo menjadi pusat data. Saat pertama kali jalan, file barang belum ada, sehingga IsiDataContoh mengisi delapan barang contoh seperti Kopi Sachet Sharecode dan Gula Pasir 1 kg. Selain itu, ada tiga method pendek SimpanBarang, SimpanTransaksi, dan SimpanPengaturan yang masing-masing hanya meneruskan list ke Simpan.

Kode Barang Otomatis BRG001

    ''' <summary>BRG001, BRG002, ... melanjutkan nomor tertinggi.</summary>
    Public Function KodeBarangBerikutnya() As String
        Dim tertinggi = 0
        For Each b In DaftarBarang
            Dim nomor As Integer
            If b.Kode.StartsWith("BRG", StringComparison.OrdinalIgnoreCase) AndAlso
               Integer.TryParse(b.Kode.Substring(3), nomor) AndAlso nomor > tertinggi Then
                tertinggi = nomor
            End If
        Next
        Return "BRG" & (tertinggi + 1).ToString("000")
    End Function

Fungsi ini mencari angka tertinggi di belakang awalan BRG, lalu menambah satu. Karena memakai nilai tertinggi, bukan jumlah barang, kode tidak akan bentrok walau Anda pernah menghapus BRG003. Format "000" menjaga angka tetap tiga digit, misalnya BRG009.

Nomor Nota Harian TRX-yyyyMMdd-001

    ''' <summary>TRX-20261107-001, nomor urut di-reset tiap hari.</summary>
    Public Function NoNotaBerikutnya() As String
        Dim awalan = "TRX-" & Date.Today.ToString("yyyyMMdd") & "-"
        Dim tertinggi = 0
        For Each t In DaftarTransaksi.Where(Function(x) x.NoNota.StartsWith(awalan))
            Dim nomor As Integer
            If Integer.TryParse(t.NoNota.Substring(awalan.Length), nomor) AndAlso nomor > tertinggi Then
                tertinggi = nomor
            End If
        Next
        Return awalan & (tertinggi + 1).ToString("000")
    End Function

Logikanya mirip kode barang, namun awalan memuat tanggal hari ini. Akibatnya, setiap pagi nomor urut kembali ke 001 secara alami tanpa perlu tabel penghitung. Nomor nota seperti TRX-20261108-005 juga langsung memberi tahu kasir tanggal transaksi dan urutannya.

Langkah 4: Program.vb dan Fungsi Bantu

Mengatur Budaya Indonesia di Sub Main

Friend Module Program
    <STAThread>
    Friend Sub Main()
        ' Format angka & tanggal Indonesia (15.000, Senin 07 November 2026)
        Dim budaya As New CultureInfo("id-ID")
        CultureInfo.DefaultThreadCurrentCulture = budaya
        CultureInfo.DefaultThreadCurrentUICulture = budaya
        Thread.CurrentThread.CurrentCulture = budaya
        Thread.CurrentThread.CurrentUICulture = budaya

        Application.SetHighDpiMode(HighDpiMode.SystemAware)
        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)

        ' Kesalahan tak terduga muncul sebagai pesan, aplikasi tidak langsung tertutup
        AddHandler Application.ThreadException,
            Sub(s, e) Galat("Terjadi kesalahan: " & e.Exception.Message)

        Repo.MuatSemua()
        Application.Run(New FormUtama())
    End Sub
End Module

Budaya id-ID membuat format N0 menghasilkan 15.000 dengan titik ribuan, dan nama hari tampil dalam Bahasa Indonesia. Setelah itu, handler ThreadException menangkap error tak terduga agar kasir cukup melihat pesan, bukan jendela crash. Terakhir, Repo.MuatSemua berjalan sebelum form utama muncul.

Format Rupiah dan Membaca Angka

Public Module Util
    Public ReadOnly Indo As New CultureInfo("id-ID")

    Public Function Rupiah(nilai As Decimal) As String
        Return "Rp " & nilai.ToString("N0", Indo)
    End Function

    ''' <summary>"Rp 15.000" / "15.000" / "15000" menjadi 15000.</summary>
    Public Function ParseAngka(teks As String) As Decimal
        Dim bersih = teks.Replace("Rp", "").Replace(" ", "").Trim()
        Dim hasil As Decimal
        If Decimal.TryParse(bersih, NumberStyles.Number, Indo, hasil) Then Return hasil
        Return 0D
    End Function
    ' ... Info, Peringatan, Galat, Tanya = pembungkus MessageBox

Kedua fungsi ini selalu memakai budaya Indonesia secara eksplisit. Jadi, walaupun Windows di komputer kasir berbahasa Inggris, teks “15.000” tetap terbaca sebagai lima belas ribu, bukan lima belas koma nol.

TextBox Nominal dengan Titik Ribuan Otomatis

    Public Sub PasangInputRupiah(tb As TextBox)
        tb.MaxLength = 15
        AddHandler tb.KeyPress,
            Sub(s As Object, e As KeyPressEventArgs)
                If Not Char.IsControl(e.KeyChar) AndAlso Not Char.IsDigit(e.KeyChar) Then e.Handled = True
            End Sub

        AddHandler tb.TextChanged,
            Sub(s As Object, e As EventArgs)
                Dim digit = New String(tb.Text.Where(Function(c) Char.IsDigit(c)).ToArray())
                If digit.Length > 15 Then digit = digit.Substring(0, 15)
                Dim rapi = If(digit = "", "", Decimal.Parse(digit, Indo).ToString("N0", Indo))
                If tb.Text <> rapi Then
                    tb.Text = rapi
                    tb.SelectionStart = tb.Text.Length
                End If
            End Sub
    End Sub

Prosedur ini memasang dua event sekaligus. Event KeyPress menolak huruf, sedangkan TextChanged merapikan angka menjadi 50.000 saat kasir mengetik 50000. Karena itu, kita cukup memanggil PasangInputRupiah(txtBayar) di form mana pun yang butuh input uang.

Langkah 5: FormUtama sebagai Dasbor

Desain Kontrol Dasbor

Nama kontrolTipeFungsi
lblToko, lblJamLabelNama toko dan jam berjalan di header
btnTransaksi s.d. btnKeluarButtonMenu kiri: transaksi, barang, riwayat, pengaturan, keluar
lblPenjualan, lblJmlTrxLabelKartu omzet dan jumlah transaksi hari ini
lblJmlBarang, lblStokMenipisLabelKartu jenis barang dan stok menipis
dgvStokDataGridViewTabel barang dengan stok ≤ 5
tmrJamTimerInterval 1000 ms untuk memperbarui jam

Atur juga KeyPreview = True pada form agar tombol F1 sampai F3 tertangkap meskipun fokus sedang berada di tombol lain.

Dasbor aplikasi kasir VB.NET dengan penjualan hari ini dan stok menipis
Dasbor SC Kasir: empat kartu ringkasan dan tabel barang yang stoknya perlu segera Anda tambah.

Menghitung Ringkasan Hari Ini

    Private Sub PerbaruiDasbor()
        lblToko.Text = Toko.NamaToko & "  ·  " & Toko.Alamat

        Dim hariIni = DaftarTransaksi.Where(Function(t) t.Tanggal.Date = Date.Today).ToList()
        lblPenjualan.Text = Rupiah(hariIni.Sum(Function(t) t.Total))
        lblJmlTrx.Text = hariIni.Count.ToString()
        lblJmlBarang.Text = DaftarBarang.Count.ToString()

        Dim menipis = DaftarBarang.Where(Function(b) b.Stok <= BATAS_STOK_MENIPIS) _
                                  .OrderBy(Function(b) b.Stok).ToList()
        lblStokMenipis.Text = menipis.Count.ToString()

        dgvStok.Rows.Clear()
        For Each b In menipis
            dgvStok.Rows.Add(b.Kode, b.Nama, $"{b.Stok} {b.Satuan}")
        Next
        dgvStok.ClearSelection()
    End Sub

Method ini menyaring transaksi yang tanggalnya sama dengan hari ini, lalu menjumlahkan totalnya. Kemudian, barang dengan stok lima atau kurang masuk ke tabel, terurut dari yang paling sedikit. Hasilnya, pemilik toko langsung tahu barang apa yang harus ia beli besok pagi.

Membuka Form Lain dan Pintasan Keyboard

    Private Sub BukaForm(f As Form)
        Using f
            f.ShowDialog(Me)
        End Using
        PerbaruiDasbor()
    End Sub

    ' ... handler tombol menu memanggil BukaForm(New FormTransaksi()) dan seterusnya

    Private Sub FormUtama_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
        Select Case e.KeyCode
            Case Keys.F1 : btnTransaksi.PerformClick()
            Case Keys.F2 : btnBarang.PerformClick()
            Case Keys.F3 : btnRiwayat.PerformClick()
        End Select
    End Sub

Semua form anak tampil sebagai dialog. Begitu form tertutup, Using membersihkan resource-nya, lalu dasbor menyegarkan angka. Dengan pola ini, omzet di dasbor selalu ikut naik setiap kali kasir selesai menyimpan transaksi.

Langkah 6: FormBarang untuk Kelola Data Barang

Desain Kontrol Form Barang

Nama kontrolTipeFungsi
txtKodeTextBoxKode atau barcode, terisi otomatis BRG berikutnya
txtNamaTextBoxNama barang
cboSatuanComboBoxpcs, bungkus, botol, sachet, kg, liter, dus, lusin
txtHargaTextBoxHarga jual dengan titik ribuan otomatis
numStokNumericUpDownJumlah stok
btnSimpan, btnBaru, btnHapusButtonSimpan (Ctrl+S), kosongkan form, hapus barang
txtCari, dgvBarangTextBox, DataGridViewPencarian langsung dan tabel barang
Form data barang aplikasi kasir VB.NET dengan kode otomatis BRG009
Form Data Barang. Kode BRG009 terisi otomatis, sedangkan baris merah menandai stok menipis.

Menampilkan dan Mencari Barang

    Private Sub TampilkanData()
        Dim kata = txtCari.Text.Trim()
        Dim data = DaftarBarang.AsEnumerable()
        If kata <> "" Then
            data = data.Where(Function(b) b.Kode.Contains(kata, StringComparison.OrdinalIgnoreCase) OrElse
                                          b.Nama.Contains(kata, StringComparison.OrdinalIgnoreCase))
        End If

        dgvBarang.Rows.Clear()
        For Each b In data.OrderBy(Function(x) x.Kode)
            Dim i = dgvBarang.Rows.Add(b.Kode, b.Nama, b.Satuan, Rupiah(b.Harga), b.Stok)
            If b.Stok <= BATAS_STOK_MENIPIS Then dgvBarang.Rows(i).DefaultCellStyle.ForeColor = Warna.Bahaya
        Next
        dgvBarang.ClearSelection()
        lblJumlah.Text = $"{dgvBarang.Rows.Count} dari {DaftarBarang.Count} barang"
    End Sub

Event txtCari_TextChanged memanggil method ini setiap kali huruf bertambah, sehingga pencarian terasa instan. Selain itu, baris dengan stok menipis berubah merah. Klik salah satu baris, maka IsiForm memindahkan datanya ke panel kiri dan mengunci kolom kode.

Menyimpan Barang Baru atau Perubahan

    Private Sub Simpan()
        Dim kode = txtKode.Text.Trim().ToUpper()
        Dim nama = txtNama.Text.Trim()
        Dim satuan = cboSatuan.Text.Trim().ToLower()
        Dim harga = ParseAngka(txtHarga.Text)

        If kode = "" Then Peringatan("Kode barang wajib diisi.") : txtKode.Focus() : Return
        If nama = "" Then Peringatan("Nama barang wajib diisi.") : txtNama.Focus() : Return
        If satuan = "" Then satuan = "pcs"
        If harga <= 0 Then Peringatan("Harga jual harus lebih dari 0.") : txtHarga.Focus() : Return

        Dim b = CariBarang(kode)
        If b Is Nothing Then
            b = New Barang With {.Kode = kode}
            DaftarBarang.Add(b)
        ElseIf Not _modeUbah Then
            If Not Tanya($"Kode {kode} sudah dipakai oleh ""{b.Nama}"".{vbCrLf}Timpa data barang tersebut?") Then Return
        End If

        b.Nama = nama
        b.Satuan = satuan
        b.Harga = harga
        b.Stok = CInt(numStok.Value)
        SimpanBarang()
        ' ... TampilkanData(), BarangBaru(), tampilkan info tersimpan
    End Sub

Validasi berjalan lebih dulu, lalu CariBarang menentukan apakah ini barang baru atau barang lama. Jika kasir mengetik kode yang sudah ada saat mode tambah, aplikasi bertanya sebelum menimpa. Setelah itu, SimpanBarang langsung menulis barang.json, jadi tidak ada tombol “simpan ke database” terpisah.

Langkah 7: FormTransaksi dengan Keranjang DataGridView

Desain Kontrol Layar Kasir

Nama kontrolTipeFungsi
lblNota, lblTanggalLabelNomor nota berjalan dan tanggal
cboBarangComboBoxKetik kode atau nama dengan autocomplete
numQty, btnTambahNumericUpDown, ButtonJumlah dan tombol masuk keranjang
dgvKeranjangDataGridViewKolom Kode, Nama, Harga, Qty (bisa edit), Subtotal
lblTotal, lblItemLabelTotal belanja dan jumlah item
txtBayar, btnUangPasTextBox, ButtonUang dari pembeli dan isian cepat uang pas
lblKembaliLabelKembalian hijau, atau kekurangan merah
chkCetak, btnSimpan, btnBatalCheckBox, ButtonOpsi struk, simpan (F10), kosongkan
Alur transaksi aplikasi kasir VB.NET dari pilih barang sampai cetak struk
Delapan tahap satu transaksi, dari mengetik kode barang sampai struk keluar dari printer.

Menyiapkan Keranjang saat Form Terbuka

    Private ReadOnly _keranjang As New List(Of ItemTransaksi)()
    Private _noNota As String = ""

    Private Sub FormTransaksi_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        GayaGrid(dgvKeranjang)
        RataKanan(colHarga, colQty, colSubtotal)

        ' Hanya kolom Qty yang boleh diedit langsung di tabel
        dgvKeranjang.ReadOnly = False
        For Each kolom As DataGridViewColumn In dgvKeranjang.Columns
            kolom.ReadOnly = kolom IsNot colQty
        Next
        colQty.DefaultCellStyle.BackColor = Color.FromArgb(254, 249, 195)

        cboBarang.DisplayMember = NameOf(Barang.Tampilan)
        cboBarang.DataSource = DaftarBarang.OrderBy(Function(b) b.Nama).ToList()
        cboBarang.AutoCompleteMode = AutoCompleteMode.SuggestAppend
        cboBarang.AutoCompleteSource = AutoCompleteSource.ListItems

        PasangInputRupiah(txtBayar)
        TransaksiBaru()
    End Sub

Keranjang berupa List(Of ItemTransaksi) biasa, sedangkan grid hanya cermin tampilannya. Kolom Qty berwarna kuning muda sebagai tanda bahwa kasir boleh mengubahnya. Kemudian, ComboBox mengambil sumber data dari daftar barang dengan autocomplete, jadi mengetik “mie” langsung menawarkan Mie Instan Goreng.

Mengenali Barang dari Isi ComboBox

    ''' <summary>Cari barang dari isi ComboBox: item terpilih, kode, atau nama persis.</summary>
    Private Function BarangTerpilih() As Barang
        Dim teks = cboBarang.Text.Trim()
        If teks = "" Then Return Nothing

        Dim terpilih = TryCast(cboBarang.SelectedItem, Barang)
        If terpilih IsNot Nothing AndAlso terpilih.Tampilan.Equals(teks, StringComparison.OrdinalIgnoreCase) Then
            Return terpilih
        End If

        Dim hasil = CariBarang(teks)
        If hasil IsNot Nothing Then Return hasil

        Return DaftarBarang.FirstOrDefault(
            Function(b) b.Tampilan.Equals(teks, StringComparison.OrdinalIgnoreCase) OrElse
                        b.Nama.Equals(teks, StringComparison.OrdinalIgnoreCase))
    End Function

Kasir punya tiga cara memasukkan barang, dan fungsi ini melayani ketiganya. Pertama, memilih item dari daftar autocomplete. Kedua, mengetik kode seperti brg001 dengan huruf kecil. Ketiga, mengetik nama lengkap seperti “Mie Instan Goreng”. Urutan pemeriksaan ini juga membuat scanner barcode langsung cocok, karena scanner sebenarnya hanya mengetik kode lalu menekan Enter.

Menambah Barang ke Keranjang

    Private Sub TambahKeKeranjang(b As Barang, qty As Integer)
        Dim item = _keranjang.FirstOrDefault(Function(i) i.Kode = b.Kode)
        Dim qtyBaru = qty + If(item Is Nothing, 0, item.Qty)

        If qtyBaru > b.Stok Then
            Peringatan($"Stok {b.Nama} tidak cukup.{vbCrLf}Sisa stok: {b.Stok} {b.Satuan}.")
            Return
        End If

        If item Is Nothing Then
            _keranjang.Add(New ItemTransaksi With {.Kode = b.Kode, .Nama = b.Nama, .Harga = b.Harga, .Qty = qty})
        Else
            item.Qty = qtyBaru
        End If

        TampilkanKeranjang()
        KosongkanInput()
    End Sub

Jika barang yang sama masuk dua kali, aplikasi menggabungkan qty-nya alih-alih membuat baris baru. Dalam screenshot di awal artikel, kasir memasukkan kopi sachet 3 lalu 2, dan keranjang menampilkan satu baris berisi 5. Sebelum itu, fungsi ini juga menolak jumlah yang melebihi stok.

Menghitung Total dan Kembalian

    Private Function TotalBelanja() As Decimal
        Return _keranjang.Sum(Function(i) i.Subtotal)
    End Function

    Private Sub HitungKembalian()
        If txtBayar.Text.Trim() = "" Then
            lblKembali.Text = "Rp 0"
            lblKembali.ForeColor = Warna.Abu
            Return
        End If

        Dim kembali = ParseAngka(txtBayar.Text) - TotalBelanja()
        If kembali < 0 Then
            lblKembali.Text = "Kurang " & Rupiah(-kembali)
            lblKembali.ForeColor = Warna.Bahaya
        Else
            lblKembali.Text = Rupiah(kembali)
            lblKembali.ForeColor = Warna.Sukses
        End If
    End Sub

Event txtBayar_TextChanged memanggil HitungKembalian pada setiap ketikan. Misalnya, total Rp 24.500 dan pembeli menyerahkan Rp 50.000, maka label langsung menulis Rp 25.500 berwarna hijau. Sebaliknya, jika uang kurang, label berubah merah dengan tulisan “Kurang Rp …”. Tombol Uang Pas cukup mengisi txtBayar dengan total.

Mengubah Qty Langsung di Tabel

    Private Sub dgvKeranjang_CellEndEdit(sender As Object, e As DataGridViewCellEventArgs) Handles dgvKeranjang.CellEndEdit
        If e.RowIndex < 0 OrElse e.RowIndex >= _keranjang.Count OrElse e.ColumnIndex <> colQty.Index Then Return

        Dim item = _keranjang(e.RowIndex)
        Dim b = CariBarang(item.Kode)
        Dim qty As Integer
        Dim teks = Convert.ToString(dgvKeranjang.Rows(e.RowIndex).Cells(e.ColumnIndex).Value)

        If Not Integer.TryParse(teks, qty) OrElse qty < 1 Then
            Peringatan("Qty harus angka minimal 1.")
        ElseIf b IsNot Nothing AndAlso qty > b.Stok Then
            Peringatan($"Stok {b.Nama} hanya {b.Stok} {b.Satuan}.")
        Else
            item.Qty = qty
        End If

        ' Tabel tidak boleh diisi ulang di dalam event-nya sendiri, jadi ditunda sebentar
        BeginInvoke(New Action(AddressOf TampilkanKeranjang))
    End Sub

Bagian paling menarik ada di baris terakhir. Kita tidak boleh menghapus dan mengisi ulang baris grid di tengah event CellEndEdit, karena DataGridView akan melempar exception. Oleh karena itu, BeginInvoke menunda penyegaran sampai event selesai.

Validasi Sebelum Menyimpan Transaksi

    Private Sub SimpanTransaksiIni()
        If _keranjang.Count = 0 Then
            Peringatan("Keranjang masih kosong.")
            cboBarang.Focus()
            Return
        End If

        Dim total = TotalBelanja()
        Dim bayar = ParseAngka(txtBayar.Text)
        If bayar < total Then
            Peringatan("Uang dibayar kurang dari total belanja.")
            txtBayar.Focus()
            Return
        End If

        ' Cek stok sekali lagi sebelum disimpan
        For Each i In _keranjang
            Dim b = CariBarang(i.Kode)
            If b Is Nothing OrElse b.Stok < i.Qty Then
                Peringatan($"Stok {i.Nama} tidak mencukupi atau barang sudah dihapus.")
                Return
            End If
        Next

Ada tiga pintu pemeriksaan: keranjang tidak boleh kosong, uang bayar harus cukup, dan stok harus masih tersedia. Pemeriksaan stok ulang ini berguna jika pemilik toko sempat mengubah data barang di tengah transaksi.

Mengurangi Stok, Menulis JSON, dan Memanggil Struk

        For Each i In _keranjang
            CariBarang(i.Kode).Stok -= i.Qty
        Next

        Dim trx As New Transaksi With {
            .NoNota = _noNota,
            .Tanggal = DateTime.Now,
            .Bayar = bayar,
            .Items = _keranjang.Select(Function(i) New ItemTransaksi With {
                .Kode = i.Kode, .Nama = i.Nama, .Harga = i.Harga, .Qty = i.Qty}).ToList()
        }
        DaftarTransaksi.Add(trx)
        SimpanTransaksi()
        SimpanBarang()

        If chkCetak.Checked Then
            Struk.Tampilkan(trx, Me)
        Else
            Info($"Transaksi {trx.NoNota} tersimpan.{vbCrLf}Kembalian: {Rupiah(trx.Kembali)}")
        End If

        TransaksiBaru()
    End Sub

Setelah semua lolos, stok setiap barang berkurang sesuai qty. Kemudian, kode menyalin isi keranjang ke objek Transaksi baru, menambahkannya ke list, lalu menulis dua file JSON. Jika kotak centang struk aktif, Print Preview langsung muncul. Terakhir, TransaksiBaru mengosongkan keranjang dan mengambil nomor nota berikutnya.

Pintasan F10 dan Esc

    Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
        If keyData = Keys.F10 Then
            SimpanTransaksiIni()
            Return True
        End If
        If keyData = Keys.Escape AndAlso Not dgvKeranjang.IsCurrentCellInEditMode Then
            Close()
            Return True
        End If
        Return MyBase.ProcessCmdKey(msg, keyData)
    End Function

Kasir yang cepat jarang menyentuh mouse. Karena itu, ProcessCmdKey menangkap F10 untuk menyimpan dan Esc untuk menutup, kecuali saat sel Qty sedang dalam mode edit. Selain itu, Enter di cboBarang menambah barang, dan Enter di kolom bayar langsung menyimpan transaksi.

Langkah 8: Cetak Struk dengan PrintDocument

Menyusun Baris Struk 32 Karakter

Printer thermal 58 mm muat sekitar 32 karakter per baris dengan huruf monospace. Oleh karena itu, class Struk menyusun teks per baris terlebih dahulu.

Public Class Struk
    Private Const LEBAR As Integer = 32  ' jumlah karakter per baris (58 mm)
    ' ... field _trx, _baris, constructor, dan Shared Sub Tampilkan(trx, pemilik)

    Private Function SusunBaris() As List(Of String)
        Dim b As New List(Of String)()
        b.Add(Tengah(Toko.NamaToko.ToUpper()))
        If Toko.Alamat.Trim() <> "" Then b.Add(Tengah(Toko.Alamat))
        If Toko.Telepon.Trim() <> "" Then b.Add(Tengah("Telp. " & Toko.Telepon))
        b.Add(New String("="c, LEBAR))
        b.Add("Nota : " & _trx.NoNota)
        b.Add("Tgl  : " & _trx.Tanggal.ToString("dd/MM/yyyy HH:mm"))
        b.Add(New String("-"c, LEBAR))

        For Each i In _trx.Items
            b.Add(Potong(i.Nama, LEBAR))
            b.Add(KiriKanan($"  {i.Qty} x {i.Harga:N0}", i.Subtotal.ToString("N0")))
        Next

        b.Add(New String("-"c, LEBAR))
        b.Add(KiriKanan("TOTAL", Rupiah(_trx.Total)))
        b.Add(KiriKanan("BAYAR", Rupiah(_trx.Bayar)))
        b.Add(KiriKanan("KEMBALI", Rupiah(_trx.Kembali)))
        b.Add(New String("="c, LEBAR))
        If Toko.PesanStruk.Trim() <> "" Then b.Add(Tengah(Toko.PesanStruk))
        Return b
    End Function

Struk terbagi menjadi kepala toko, info nota, daftar barang, dan blok total. Setiap barang memakai dua baris: nama di atas, lalu qty kali harga di kiri dan subtotal di kanan. Identitas toko berasal dari objek Toko, sehingga perubahan di menu pengaturan langsung terlihat di struk berikutnya.

Fungsi Rata Tengah dan Kiri-Kanan

    Private Shared Function Potong(teks As String, panjang As Integer) As String
        Return If(teks.Length > panjang, teks.Substring(0, panjang), teks)
    End Function

    Private Shared Function Tengah(teks As String) As String
        teks = Potong(teks.Trim(), LEBAR)
        Return New String(" "c, (LEBAR - teks.Length) \ 2) & teks
    End Function

    ' ... teks kiri, spasi secukupnya, lalu teks kanan
    Private Shared Function KiriKanan(kiri As String, kanan As String) As String
        Dim spasi = LEBAR - kiri.Length - kanan.Length
        If spasi < 1 Then spasi = 1
        Return kiri & New String(" "c, spasi) & kanan
    End Function

Tiga fungsi kecil ini adalah inti tata letak struk. Tengah menambah spasi di kiri sebanyak separuh sisa lebar. Sementara itu, KiriKanan mengisi celah dengan spasi agar angka rapi di tepi kanan. Trik ini hanya bekerja dengan font monospace seperti Consolas.

Contoh struk 58 mm hasil aplikasi kasir VB.NET Toko Sharecode Mandiri
Struk 58 mm untuk nota TRX-20261108-005: 4 teh botol dan 1 kg gula, bayar Rp 50.000, kembali Rp 13.000.

Mengatur Kertas 58 mm di PrintDocument

    Private Sub Pratinjau(pemilik As IWin32Window)
        Using huruf As New Font("Consolas", 7.5F),
              doc As New PrintDocument(),
              dlg As New PrintPreviewDialog()

            ' Satuan kertas = 1/100 inci. Lebar 58 mm = 228.
            Dim tinggiBaris = huruf.GetHeight(100)
            Dim tinggiKertas = CInt(40 + _baris.Count * tinggiBaris)

            doc.DocumentName = "Struk " & _trx.NoNota
            doc.DefaultPageSettings.PaperSize = New PaperSize("Struk 58mm", 228, Math.Max(tinggiKertas, 200))
            doc.DefaultPageSettings.Margins = New Margins(10, 10, 15, 15)

Satuan kertas di .NET adalah seperseratus inci, jadi 58 mm setara 228. Tinggi kertas mengikuti jumlah baris, sehingga struk panjang tidak terpotong dan struk pendek tidak membuang kertas.

Menggambar Teks di Event PrintPage

            AddHandler doc.PrintPage,
                Sub(s As Object, e As PrintPageEventArgs)
                    Dim y As Single = e.MarginBounds.Top
                    For Each baris In _baris
                        e.Graphics.DrawString(baris, huruf, Brushes.Black, e.MarginBounds.Left, y)
                        y += tinggiBaris
                    Next
                    e.HasMorePages = False
                End Sub

            dlg.Document = doc
            ' ... judul, ukuran, posisi, dan antialias dialog
            dlg.PrintPreviewControl.Zoom = 1.5
            Try
                dlg.ShowDialog(pemilik)
            Catch ex As InvalidPrinterException
                Peringatan("Tidak ada printer yang terpasang. Pasang printer (atau Microsoft Print to PDF) untuk mencetak struk.")
            End Try
        End Using
    End Sub

Event PrintPage menggambar setiap baris memakai DrawString, lalu menurunkan posisi y sebesar tinggi satu baris. Kemudian, PrintPreviewDialog memperlihatkan hasilnya. Dari jendela itu kasir bisa menekan ikon printer untuk mencetak ke printer thermal atau ke Microsoft Print to PDF. Jika belum ada printer sama sekali, aplikasi menampilkan peringatan, bukan crash.

Langkah 9: Riwayat Transaksi dan Ekspor CSV

Menyaring Transaksi per Rentang Tanggal

    Private Sub Tampilkan()
        Dim dari = dtpDari.Value.Date
        Dim sampai = dtpSampai.Value.Date.AddDays(1)   ' eksklusif, agar seluruh hari terakhir ikut
        If dari >= sampai Then
            Peringatan("Tanggal awal tidak boleh melewati tanggal akhir.")
            Return
        End If

        Dim cari = txtCari.Text.Trim()
        _hasil = DaftarTransaksi.Where(Function(t) t.Tanggal >= dari AndAlso t.Tanggal < sampai AndAlso
                                                   (cari = "" OrElse t.NoNota.Contains(cari, StringComparison.OrdinalIgnoreCase))) _
                                .OrderByDescending(Function(t) t.Tanggal).ToList()

        ' ... isi dgvTransaksi: nota, tanggal, item, total, bayar, kembali
        lblRingkas.Text = $"{_hasil.Count} transaksi  ·  {_hasil.Sum(Function(t) t.JumlahItem)} item terjual  ·  " &
                          $"Omzet {Rupiah(_hasil.Sum(Function(t) t.Total))}"
        TampilkanDetail()
    End Sub

Batas akhir sengaja kita tambah satu hari lalu memakai tanda kurang dari. Tanpa trik itu, transaksi pukul 13.42 pada tanggal akhir akan hilang, karena Value.Date bernilai pukul 00.00. Selanjutnya, label ringkasan menghitung jumlah transaksi, item terjual, dan omzet dari hasil filter.

Riwayat penjualan aplikasi kasir VB.NET dengan detail barang dan omzet
Riwayat Penjualan: lima transaksi hari ini, detail nota terpilih di kanan, dan omzet di bagian bawah.

Detail Barang dan Cetak Ulang Struk

    Private Function TransaksiTerpilih() As Transaksi
        If dgvTransaksi.CurrentRow Is Nothing Then Return Nothing
        Dim idx = dgvTransaksi.CurrentRow.Index
        If idx < 0 OrElse idx >= _hasil.Count Then Return Nothing
        Return _hasil(idx)
    End Function

    ' ... TampilkanDetail() mengisi dgvDetail dari t.Items

    Private Sub btnCetak_Click(sender As Object, e As EventArgs) Handles btnCetak.Click
        Dim t = TransaksiTerpilih()
        If t Is Nothing Then
            Peringatan("Pilih transaksi yang akan dicetak.")
            Return
        End If
        Struk.Tampilkan(t, Me)
    End Sub

Indeks baris grid selalu sejajar dengan list _hasil, sebab keduanya berasal dari urutan yang sama. Karena itu, kita cukup mengambil _hasil(idx) tanpa mencari ulang berdasarkan nomor nota. Setiap kali pilihan baris berubah, event SelectionChanged memanggil TampilkanDetail sehingga tabel kanan ikut berganti. Fitur cetak ulang ini berguna ketika pembeli kembali meminta struk untuk klaim garansi atau laporan kantor.

Ekspor ke CSV untuk Excel

            Dim baris As New List(Of String) From {"No Nota;Tanggal;Kode;Nama Barang;Harga;Qty;Subtotal"}
            For Each t In _hasil.OrderBy(Function(x) x.Tanggal)
                For Each i In t.Items
                    baris.Add(String.Join(";", Csv(t.NoNota), Csv(t.Tanggal.ToString("yyyy-MM-dd HH:mm")),
                                          Csv(i.Kode), Csv(i.Nama), Csv(i.Harga), Csv(i.Qty), Csv(i.Subtotal)))
                Next
            Next
            ' UTF-8 dengan BOM agar huruf terbaca benar saat dibuka di Excel
            File.WriteAllLines(dlg.FileName, baris, New UTF8Encoding(True))

Ekspor memakai pemisah titik koma karena Excel berbahasa Indonesia memakai koma sebagai tanda desimal. Setiap baris CSV mewakili satu barang dalam satu nota, sehingga Anda bisa membuat PivotTable per barang dengan mudah. Klik dua kali baris transaksi juga memicu Cetak Ulang Struk lewat Struk.Tampilkan yang sama.

Langkah 10: Pengaturan Toko

    Private Sub btnSimpan_Click(sender As Object, e As EventArgs) Handles btnSimpan.Click
        If txtNamaToko.Text.Trim() = "" Then
            Peringatan("Nama toko wajib diisi.")
            txtNamaToko.Focus()
            Return
        End If

        Toko.NamaToko = txtNamaToko.Text.Trim()
        Toko.Alamat = txtAlamat.Text.Trim()
        Toko.Telepon = txtTelepon.Text.Trim()
        Toko.PesanStruk = txtPesan.Text.Trim()
        SimpanPengaturan()

        DialogResult = DialogResult.OK
        Close()
    End Sub

Form ini berisi empat TextBox (txtNamaToko, txtAlamat, txtTelepon, txtPesan) dan label lokasi folder data. Saat kasir menekan tombol Simpan, nilai baru masuk ke objek Toko lalu tertulis ke pengaturan.json. Label folder data juga membantu pemilik toko saat ingin membuat cadangan.

Form pengaturan toko pada aplikasi kasir VB.NET
Pengaturan Toko: identitas yang tampil di dasbor dan struk.

Menjalankan dan Membagikan Aplikasi

Tekan F5 di Visual Studio untuk menjalankan. Selain itu, Anda juga bisa memakai terminal di folder solution dengan perintah berikut.

dotnet run --project SCKasir

dotnet publish SCKasir -c Release -r win-x64 --self-contained false -o publish

Perintah kedua menghasilkan folder publish berisi SCKasir.exe. Komputer kasir cukup memasang .NET 8 Desktop Runtime. Jika ingin tanpa instal runtime, ganti menjadi --self-contained true, walau ukuran file menjadi lebih besar.

Contoh Kasus 1: Toko Sharecode Mandiri Melayani Pembeli

Pukul 13.42, seorang pembeli datang ke Toko Sharecode Mandiri membawa 4 teh botol dan 1 kg gula pasir. Kasir menekan F1 dari dasbor, lalu mengetik BRG002, mengisi qty 4, dan menekan Enter. Setelah itu, ia mengetik “gula”, memilih Gula Pasir 1 kg dari autocomplete, lalu Enter lagi.

Total langsung tertulis Rp 37.000. Pembeli menyerahkan selembar Rp 50.000, sehingga kasir mengetik 50000 di kolom bayar dan aplikasi menampilkan kembalian Rp 13.000 berwarna hijau. Kemudian, kasir menekan Enter. Stok teh botol turun dari 48 menjadi 44, dan Print Preview muncul dengan nota TRX-20261108-005.

Agar struk keluar dari printer thermal 58 mm, pastikan driver printer sudah terpasang dan ukuran kertasnya 58 mm. Selanjutnya, klik ikon printer di jendela pratinjau. Jika toko belum punya printer, pilih Microsoft Print to PDF lalu kirim file PDF-nya ke pembeli lewat WhatsApp.

Setelah pembeli pergi, layar kasir sudah siap dengan nota TRX-20261108-006 dan keranjang kosong. Sementara itu, jika kasir kembali ke dasbor, kartu penjualan hari ini ikut bertambah Rp 37.000. Kebetulan stok gula pasir kini tinggal 14, jadi belum masuk daftar stok menipis. Namun, sabun mandi dan roti tawar sudah muncul di tabel stok menipis, sehingga pemilik toko tahu barang apa yang perlu ia pesan ke grosir.

Contoh Kasus 2: Rekap Penjualan Harian dari Riwayat

Menjelang tutup toko, pemilik ingin mencocokkan uang di laci dengan catatan aplikasi. Pertama, ia menekan F3 untuk membuka Riwayat Penjualan. Kemudian, ia mengatur tanggal Dari dan s.d. ke hari ini lalu klik Tampilkan. Label bawah menulis “5 transaksi · 25 item terjual · Omzet Rp 87.500”.

Angka omzet itulah yang harus sama dengan selisih uang laci antara pagi dan malam. Jika ada selisih, ia cukup klik tiap nota untuk melihat detail barangnya di tabel kanan, atau cetak ulang struk tertentu. Terakhir, ia klik Ekspor CSV, menyimpan file penjualan_20261108-20261108.csv, lalu membukanya di Excel untuk menjumlah penjualan per barang. Dari situ terlihat bahwa kopi sachet paling laris hari itu, jadi besok ia menambah stoknya.

Sebelum mematikan komputer, ia juga menyalin folder data ke flashdisk. Karena seluruh data hanya berupa tiga file JSON, proses cadangan ini cukup beberapa detik. Selain itu, jika suatu hari komputer kasir rusak, ia tinggal memasang SC Kasir di komputer lain, lalu menempelkan folder data tadi di samping file exe. Dengan demikian, riwayat penjualan berbulan-bulan tetap aman tanpa perlu server atau layanan cloud berbayar.

Ide Pengembangan Lanjutan

  • Database SQLite: ganti isi modul Penyimpanan dan Repo bila transaksi sudah puluhan ribu.
  • Scanner barcode USB: alat ini bekerja seperti keyboard, jadi cukup isi kode barang dengan angka barcode lalu Enter.
  • Login kasir dan hak akses agar hanya pemilik yang boleh mengubah harga.
  • Diskon per barang atau per transaksi, plus kolom diskon di struk.
  • Laporan laba dengan menambah properti harga beli di class Barang.

Kesalahan Umum

  • Membuka folder, bukan file .sln. Gunakan File, Open, Project/Solution lalu pilih SCKasir.sln, karena Open Folder membuat project tidak bisa debug.
  • Memakai Visual Studio 2019. Versi ini tidak mendukung .NET 8, jadi perbarui ke VS 2022 17.8 atau lebih baru.
  • Lupa Option Strict. Tanpa opsi ini, VB diam-diam mengubah teks menjadi angka dan bug baru muncul saat aplikasi sudah berjalan.
  • Salah budaya angka. Parsing “15.000” dengan budaya en-US menghasilkan 15, karena itu selalu sertakan Indo pada TryParse dan ToString.
  • Menaruh aplikasi di C:\Program Files. Aplikasi biasa tidak punya izin menulis ke folder ini, sehingga file JSON gagal tersimpan. Pindahkan ke D:\SCKasir.
  • File data terkunci. Membuka transaksi.json di editor yang mengunci file bisa membuat penyimpanan gagal, jadi tutup editornya dulu.
  • Font struk bukan monospace. Mengganti Consolas dengan Arial membuat kolom angka berantakan.

Kesimpulan

Kita sudah membangun aplikasi kasir VB.NET lengkap dari nol: model data dengan properti hitung, penyimpanan JSON yang aman dari listrik padam, kode barang dan nomor nota otomatis, layar kasir dengan keranjang DataGridView, cetak struk 58 mm lewat PrintDocument, hingga riwayat penjualan dengan ekspor CSV. Semua itu cukup memakai Windows Forms dan .NET 8 tanpa satu pun paket tambahan.

Langkah terbaik berikutnya adalah mencoba sendiri. Silakan unduh source code lengkap SC Kasir VB.NET di sharecode.id secara gratis, buka SCKasir.sln, lalu tekan F5. Setelah itu, coba tambahkan satu fitur dari daftar ide di atas, misalnya diskon, agar pemahaman Anda semakin matang.

Baca Juga

Referensi: Windows Forms untuk .NET (Microsoft Learn), Kelas PrintDocument (Microsoft Learn), Kontrol DataGridView di Windows Forms (Microsoft Learn), Serialisasi JSON dengan System.Text.Json (Microsoft Learn).

Ali Akbar

Software Developer yang fokus mengembangkan aplikasi berbasis Web dan Desktop. Senang mempelajari teknologi baru terutama di bidang web design dan web development.

View all posts by Ali Akbar →

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *