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.

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
PrintDocumentdan 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
- Buka Visual Studio, lalu pilih Create a new project.
- Ketik “Windows Forms App” pada kolom pencarian, kemudian pilih template berlogo VB (bukan C#).
- Isi nama project
SCKasir, tentukan folder, lalu klik Next. - Pada pilihan framework, ambil .NET 8.0 (Long Term Support) dan klik Create.
- Hapus
Form1.vbbawaan, karena nanti kita memakaiSub Mainsendiri. - Buat folder
Formslewat 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
| File | Fungsi |
|---|---|
Program.vb | Titik masuk aplikasi, budaya id-ID, penangkap error |
Model.vb | Class Barang, ItemTransaksi, Transaksi, Pengaturan |
Penyimpanan.vb | Baca dan tulis file JSON di folder data |
Repo.vb | Data aktif, pencarian, kode barang dan nomor nota otomatis |
Util.vb | Format Rupiah, input nominal, gaya grid, kotak pesan |
Struk.vb | Susun teks struk dan cetak lewat PrintDocument |
Forms\*.vb | Lima layar: dasbor, barang, transaksi, riwayat, pengaturan |
Arsitektur Aplikasi: Form, Repo, Penyimpanan, File 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.

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 kontrol | Tipe | Fungsi |
|---|---|---|
lblToko, lblJam | Label | Nama toko dan jam berjalan di header |
btnTransaksi s.d. btnKeluar | Button | Menu kiri: transaksi, barang, riwayat, pengaturan, keluar |
lblPenjualan, lblJmlTrx | Label | Kartu omzet dan jumlah transaksi hari ini |
lblJmlBarang, lblStokMenipis | Label | Kartu jenis barang dan stok menipis |
dgvStok | DataGridView | Tabel barang dengan stok ≤ 5 |
tmrJam | Timer | Interval 1000 ms untuk memperbarui jam |
Atur juga KeyPreview = True pada form agar tombol F1 sampai F3 tertangkap meskipun fokus sedang berada di tombol lain.

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 kontrol | Tipe | Fungsi |
|---|---|---|
txtKode | TextBox | Kode atau barcode, terisi otomatis BRG berikutnya |
txtNama | TextBox | Nama barang |
cboSatuan | ComboBox | pcs, bungkus, botol, sachet, kg, liter, dus, lusin |
txtHarga | TextBox | Harga jual dengan titik ribuan otomatis |
numStok | NumericUpDown | Jumlah stok |
btnSimpan, btnBaru, btnHapus | Button | Simpan (Ctrl+S), kosongkan form, hapus barang |
txtCari, dgvBarang | TextBox, DataGridView | Pencarian langsung dan tabel barang |

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 kontrol | Tipe | Fungsi |
|---|---|---|
lblNota, lblTanggal | Label | Nomor nota berjalan dan tanggal |
cboBarang | ComboBox | Ketik kode atau nama dengan autocomplete |
numQty, btnTambah | NumericUpDown, Button | Jumlah dan tombol masuk keranjang |
dgvKeranjang | DataGridView | Kolom Kode, Nama, Harga, Qty (bisa edit), Subtotal |
lblTotal, lblItem | Label | Total belanja dan jumlah item |
txtBayar, btnUangPas | TextBox, Button | Uang dari pembeli dan isian cepat uang pas |
lblKembali | Label | Kembalian hijau, atau kekurangan merah |
chkCetak, btnSimpan, btnBatal | CheckBox, Button | Opsi struk, simpan (F10), kosongkan |

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.

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.

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.

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
IndopadaTryParsedanToString. - 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.jsondi 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
- Aplikasi Penjualan Dengan Visual Basic 6 – Part 1 – Desain Database
- Tips dan Trik VB.NET
- Menampilkan informasi CPU dan RAM menggunakan VB.NET
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).

