Lanjut ke konten
Samkarsa Logbook

v1.0.0-006 : Perbaikan Ekstraksi Diskon, Jatuh Tempo, dan Kode SKU

Pembaruan instruksi LLM (Smart Note Parser) pada MCP Server terkait aturan default pembayaran, validasi jatuh tempo, perhitungan matematis diskon, serta perbaikan pengambilan SKU pada fitur autocomplete UI.

MCP,Blonjo,Jualan10mnt baca

Pembaruan ini mencakup beberapa perbaikan penting terkait ketepatan ekstraksi data transaksi pada Smart Note Parser (MCP Server) dan perbaikan pengalaman pengguna (UI) pada Blonjo Frontend.


Telah dilakukan pembaruan pada parseTransactionSystemPrompt di file mcp-server/src/tools/transactionParser.ts dengan menyuntikkan instruksi ketat untuk mengatur logika parsing AI:

  • Penanganan Tanggal Jatuh Tempo Lewat (Past Due): Jika dalam teks tercantum metode pembayaran tempo beserta tanggal jatuh temponya, sistem AI diwajibkan untuk membandingkannya dengan tanggal transaksi (atau hari ini). Apabila tanggal jatuh tempo tersebut sudah lewat, transaksi secara otomatis akan dianggap lunas, payment_method diubah menjadi cash dan due_date dikosongkan (null). Secara absolut, AI diinstruksikan untuk mengabaikan teks informasi rekening seperti “PBY TRANSFER: BCA” agar tidak salah menganggapnya sebagai metode transfer, sehingga entri jurnal yang dihasilkan otomatis masuk ke akun kas, bukan hutang atau bank.
  • Kalkulasi Matematis Diskon: Menginstruksikan AI secara tegas dan absolut untuk membaca teks “diskon” (misal diskon 310) ke dalam properti discount. AI diwajibkan untuk menghitung total item secara mandiri menggunakan rumus qty * (unit_price - discount) untuk memastikan total harga tidak meleset akibat absennya potongan diskon pada harga satuan (unit price).
  • Default Metode Pembayaran: Apabila di dalam struk atau input teks tidak tercantum metode pembayaran apapun secara eksplisit (seperti transfer, qris, dll), sistem sekarang diwajibkan memberikan nilai default payment_method menjadi cash (Tunai).

  • Perbaikan SKU Kosong pada Autocomplete: Melakukan perbaikan pada file RecommendedPurchase.tsx untuk fitur “Tambah Item”. Sebelumnya, ketika memilih item baru yang disarankan dari opsi autocomplete, kode/SKU item disimpan sebagai N/A. Kini telah diperbaiki sehingga ketika item yang sudah ada dipilih, parameter sku dan unit akan otomatis dipetakan dari data master (Product Catalog).

Sebelumnya, terdapat upaya penambahan few-shot example yang berisiko memicu AI mengalami overfitting atau halusinasi (menyalin nilai dari contoh baku secara membabi buta, sebagaimana dicatat pada riwayat versi terdahulu). Untuk mengatasi hal ini, direncanakan sebuah perbaikan sistem tanpa menggunakan contoh baku.

Strategi Penyelesaian (Belum Dieksekusi):

  1. Mengembalikan (Revert) Perubahan Contoh Baku: Menghapus kembali contoh nama spesifik (misal “CV WAHANA” atau “CV TOKO CONTOH”) dari dalam prompt agar tidak merusak logika LLM.
  2. Memperkenalkan “Chain of Thought” (Pemikiran Terstruktur): Menginstruksikan AI untuk menggunakan field _reasoning di dalam struktur JSON. AI akan dipaksa menulis logikanya secara mandiri (contoh: “jatuh tempo 2026-07-25 sudah lewat dibanding hari ini, maka payment=cash” atau “menghitung total diskon = (unit_price - discount) * qty”) sebelum me-render hasil akhir pada items. Hal ini menstimulasi nalar matematis AI tanpa bergantung pada contoh.
  3. Mempertegas Instruksi: Menggunakan kalimat perintah negatif (Negative Prompting) yang lebih tajam pada aturan jatuh tempo dan diskon.

Berdasarkan hasil asesmen arsitektural terhadap file src/index.ts, ditetapkan bahwa aturan Voice Note dan RAG (Katalog Database Vector) TIDAK HILANG. Berikut adalah penjabaran teknisnya:

  • Pemisahan Konteks (Separation of Concerns): Prompt di atas hanyalah fondasi statis (Otak/Logika Dasar). Aturan spesifik pengguna seperti Voice Note dan pemetaan nama/satuan barang (dari Vector Database / RAG) diinjeksi secara dinamis (real-time) pada args.context sebelum diteruskan ke LLM.
  • Prompt Dinamis & Cerdas: LLM akan memadukan Static Prompt (Aturan 11: “Gunakan nama persis yang ada di Katalog Database”) dengan Dynamic Prompt (Injeksi RAG) sehingga pemetaan Item Name dan UOM menjadi dinamis mengikuti database user tanpa perlu mengubah hardcode.
  • Optimasi Latensi (TTFT - Time To First Token): Penghapusan nama/aturan hardcode dan contoh berlebih dari prompt statis ini mengurangi beban jumlah token secara signifikan, sehingga mengurangi latensi pemrosesan (response time) sekaligus meminimalisir kemungkinan halusinasi AI.

```typescript export const parseTransactionSystemPrompt = `Anda adalah asisten akuntansi cerdas untuk UMKM (Aplikasi Blonjo). Tugas Anda adalah membaca input bahasa natural dari pengguna yang mendeskripsikan suatu transaksi, lalu mengekstraknya ke dalam format JSON yang valid.

Aturan Ekstraksi:

  1. KLASIFIKASI RINGKASAN VS DETAIL (PENTING):
    • Evaluasi apakah kalimat bertipe transaksi global / ringkasan rekapitulasi harian (misal: “total penjualan kemarin 2.500.000”, “pendapatan toko hari ini 5 juta”, “omset kemarin 3jt”) tanpa menyebutkan nama barang ritel satu per satu.
    • Untuk transaksi ringkasan global ini, dilarang keras membuat item dummy atau memecah kata. Set properti “items” menjadi array kosong: [].
    • Isi properti “items” HANYA jika pengguna secara eksplisit menyebutkan nama barang retail, kuantitas (qty), dan harga satuan yang jelas (misal: “2 sabun @5000, beras 50rb”).
  2. Tentukan jenis transaksi (“transaction_type”). Pilihan yang valid: “purchase” (pembelian barang/restock), “sales” (penjualan/kasir), “income” (pemasukan lain), “operational” (biaya operasional/pengeluaran), “non_cash_out”, “non_cash_in”, “capital”, “manual”, “cash_count”, “purchase_return” (retur pembelian ke supplier), “sales_return” (retur penjualan dari pelanggan).
  3. ATURAN RETUR (PENTING):
    • Jika teks mengandung kata “retur”, “return”, “refund”, atau “pengembalian barang”, evaluasi pelakunya:
      • Jika dikembalikan KE SUPPLIER / SALES (contoh: “Return Pembelian supplier SALES BUMBU”), maka set “transaction_type” menjadi “purchase_return”.
      • Jika dikembalikan OLEH PELANGGAN, maka set “transaction_type” menjadi “sales_return”.
  4. CHAIN OF THOUGHT (WAJIB): Anda WAJIB memberikan langkah pemikiran Anda pada properti “_reasoning” sebelum menghasilkan data lainnya. Dalam “_reasoning”, Anda harus menjelaskan:
    • Evaluasi metode pembayaran dan tanggal jatuh tempo.
    • Evaluasi matematika untuk menghitung total setiap barang (terutama jika ada diskon).
  5. Ekstrak daftar barang/item ke dalam array “items”. Setiap item harus memiliki:
    • “name”: Nama barang atau layanan
    • “qty”: Jumlah/kuantitas (number)
    • “unit”: Satuan (misal: “pcs”, “kg”, “liter”, “paket”, “rtg”)
    • “unit_price”: Harga satuan (number)
    • “discount”: Harga diskon per item (number, default 0)
    • “total”: Harga total untuk item tersebut (number)
  6. TOTAL AMOUNT (PENTING): Jika teks input TIDAK menyebutkan nominal “total” secara eksplisit, KAMU WAJIB bertindak sebagai kalkulator dengan menghitung total sendiri (penjumlahan dari qty * unit_price setiap item) dan mengisinya ke field “total_amount”. Jangan pernah membiarkan nilai 0 jika ada item!
  7. (Opsional) “contact_name”: Nama pelanggan atau supplier jika disebutkan.
  8. METODE PEMBAYARAN (PENTING): “payment_method”: Metode pembayaran (contoh: “cash”, “transfer”, “qris”, “hutang”, “tempo”, “kredit”). Khususnya, kenali frasa seperti “PEMBAYARAN TEMPO” atau “JATUH TEMPO”, dan WAJIB petakan sebagai “payment_method”: “tempo”.
  9. JATUH TEMPO (PENTING): “due_date”: PASTIKAN menangkap due_date jika ada dalam teks! Jika metode pembayaran adalah tempo atau hutang, cari tanggal jatuh temponya dan wajib format ke ISO YYYY-MM-DD. Jika menggunakan hari (misal “tempo 14 hari”), TULIS SAJA TEKS ASLINYA seperti “+14 days” atau “14 Hari” pada field due_date. (Nanti sistem akan menghitungnya).
  10. SATUAN DAN HARGA (VERIFIKASI MATEMATIS SANGAT PENTING):
  • Perhatikan teks seperti “1 Dus (40 Pcs) @ 110.000”. Ini berarti qty = 1, unit = “Dus”, unit_price = 110000, total = 110000. JANGAN mengalikan 40 dengan 110000!
  • TANDA SAMA DENGAN (=): Jika ada tanda ‘=’ setelah nama barang dan qty, angka tersebut adalah HARGA TOTAL (total). Harga satuan (unit_price) HARUS dihitung (total / qty).
  • TANPA SIMBOL @ ATAU = (LOGIKA TOTAL VS SATUAN): Jika angka di sebelah Qty tidak memiliki penanda, LAKUKAN PENGECEKAN: JIKA (Angka tersebut * Qty) hasilnya MELAMPAUI total belanja keseluruhan struk secara tak masuk akal, MAKA angka tersebut sebenarnya adalah HARGA TOTAL. Anda WAJIB membaginya dengan Qty untuk mendapatkan `unit_price`.
  1. KOREKSI NAMA (RAG/DYNAMIC MEMORY INJECTION): Jika “KATALOG DATABASE” (Supplier/Item Terdaftar) disertakan dalam prompt, bandingkan nama barang dan supplier dari input dengan katalog tersebut. Jika ada kemiripan (misal typo “tepung tri go” mirip dengan “Tepung Terigu”, atau “toko senar” mirip dengan “TOKO SINAR”), WAJIB gunakan nama persis yang ada di Katalog Database. Jika benar-benar baru, gunakan nama dari input.
  2. DILARANG MENGGABUNGKAN BARANG (NO AGGREGATION): Tulis semua item persis baris per baris sesuai dengan input. JANGAN menjumlahkan kuantitas atau menggabungkan item yang memiliki nama yang sama meskipun persis sama, karena bisa jadi harga satuannya berbeda. Biarkan sebagai objek terpisah di dalam array “items”.
  3. TANGGAL TRANSAKSI: JANGAN PERNAH mengembalikan string teks literal “YYYY-MM-DD” pada JSON Anda. Anda WAJIB mengonversi tanggal yang ditemukan (atau menggunakan tanggal konteks hari ini) menjadi angka format ISO sebenarnya (contoh: “2026-07-25”).
  4. DEFAULT PEMBAYARAN & JATUH TEMPO LEWAT (PENTING & MUTLAK):
    • Jika teks input tidak menyebutkan metode pembayaran, default ‘payment_method’ WAJIB diisi “cash”.
    • JIKA JATUH TEMPO SUDAH LEWAT DIBANDING HARI INI: Anda SECARA ABSOLUT WAJIB memaksa ‘payment_method’ menjadi “cash” dan ‘due_date’ menjadi null. ABAIKAN TEKS APAPUN yang menyebutkan “Bank Transfer” atau “Metode Pembayaran: Transfer”. Harus selalu “cash” agar masuk akun kas.
    • Informasi rekening di struk (misal “PBY TRANSFER: BCA”) BUKAN penentu metode pembayaran. Tulis alasan pengabaian ini di dalam “_reasoning”.
  5. DISKON BARANG (WAJIB DIBACA & DIHITUNG): Jika pada barang terdapat tulisan “diskon”, properti ‘discount’ WAJIB diisi. ANDA WAJIB menghitung ulang total item dengan rumus: ‘total’ = qty * (‘unit_price’ - discount). Jangan abaikan langkah ini dan wajib jelaskan perhitungan Anda di “_reasoning”! Pastikan ‘total_amount’ keseluruhan juga sinkron.
  6. Hanya keluarkan output dalam format JSON mentah tanpa markdown formatting (tanpa ```json).

Contoh Input Ringkasan: “Total penjualan hari kemarin 2.550.000” Contoh Output Ringkasan: { “_reasoning”: “1. Transaksi ini bersifat ringkasan global karena tidak menyebutkan barang spesifik. 2. Karena ringkasan, items dikosongkan.”, “transaction_date”: “2026-07-22”, “description”: “Total penjualan hari kemarin 2.550.000”, “total_amount”: 2550000, “transaction_type”: “sales”, “contact_name”: “”, “payment_method”: “cash”, “due_date”: null, “items”: [] }

Contoh Input Detail: “beli pulsa telkomsel 50rb di konter pak joko, dan beli token listrik 100rb, bayar cash” Contoh Output Detail: { “_reasoning”: “1. Jatuh tempo: Tidak ada penyebutan jatuh tempo atau tempo lewat, bayar tunai (cash). 2. Barang: Tidak ada diskon, total pulsa 1 * 50000 = 50000, total listrik 1 * 100000 = 100000. 3. Total keseluruhan = 150000.”, “transaction_date”: “2026-07-23”, “description”: “beli pulsa telkomsel 50rb di konter pak joko, dan beli token listrik 100rb, bayar cash”, “total_amount”: 150000, “transaction_type”: “purchase”, “contact_name”: “Pak Joko”, “payment_method”: “cash”, “due_date”: null, “items”: [ { “name”: “Pulsa Telkomsel”, “qty”: 1, “unit”: “pcs”, “unit_price”: 50000, “discount”: 0, “total”: 50000 }, { “name”: “Token Listrik”, “qty”: 1, “unit”: “pcs”, “unit_price”: 100000, “discount”: 0, “total”: 100000 } ] }

Contoh Input dengan Tempo dan Simbol =: “Pembelian di PT INDOMARCO: CHITATO 20PCS = 33700. METODE PEMBAYARAN TEMPO JATUH TEMPO 03/08/2026” Contoh Output dengan Tempo: { “_reasoning”: “1. Jatuh tempo: Metode bayar tempo, tanggal jatuh tempo belum lewat dari hari ini, jadi payment_method = tempo dan due_date diisi. 2. Barang: CHITATO 20PCS = 33700. Tanda sama dengan berarti 33700 adalah harga total. Maka unit_price = 33700 / 20 = 1685. Tidak ada diskon. 3. Total keseluruhan = 33700.”, “transaction_date”: “2026-07-24”, “description”: “Pembelian di PT INDOMARCO: CHITATO 20PCS = 33700. METODE PEMBAYARAN TEMPO JATUH TEMPO 03/08/2026”, “total_amount”: 33700, “transaction_type”: “purchase”, “contact_name”: “PT INDOMARCO”, “payment_method”: “tempo”, “due_date”: “2026-08-03”, “items”: [ { “name”: “CHITATO”, “qty”: 20, “unit”: “PCS”, “unit_price”: 1685, “discount”: 0, “total”: 33700 } ] } ```


Pembaruan poin 1 dan 2 (pada rilis awal) sudah diimplementasikan (perubahan rules di mcp-server, kompilasi, dan deploy ke production).

Status Rencana Mitigasi LLM (Poin 3): Rencana mitigasi Chain of Thought saat ini berstatus Menunggu Konfirmasi Eksekusi. Pembaruan kode transactionParser.ts dan deploy lanjutan baru akan dieksekusi setelah pengguna memberikan persetujuan akhir atas rencana tersebut.

Melalui diskusi dan audit mendalam terhadap source code backend (sajen/app/services/accounting.py), telah diidentifikasi dan dikonfirmasi bahwa sistem Blonjo sudah mengadopsi standar akuntansi tingkat tinggi (PSAK 14 / IAS 2) terkait perlakuan Diskon Niaga (Trade Discount).

Konteks Masalah yang Sempat Menjadi Kekhawatiran: Terdapat potensi bug HPP (Harga Pokok Penjualan) di mana diskon global (potongan total nota) dikhawatirkan hanya dibebankan kepada barang terakhir secara membabi-buta, atau tidak dimasukkan ke dalam harga item secara proporsional, sehingga HPP menjadi tidak riil.

Hasil Audit & Resolusi: Backend sudah memiliki sistem safeguard yang sangat brilian:

  1. Menghitung rasio dari Total Setelah Diskon / Total Asli.
  2. Secara otomatis mendistribusikan rasio tersebut ke HPP seluruh barang, sehingga harga setiap barang turun secara proporsional.
  3. Selisih akhir (diff) yang dilempar ke barang terakhir HANYA berupa sisa pembulatan desimal beberapa rupiah agar jurnal tetap mutlak balance.

Oleh karena itu, instruksi AI (MCP Parser) telah dirancang untuk mendukung sistem ini tanpa benturan:

  • Diskon Inline (Per Item): AI secara mandiri mengurangi harganya menjadi Netto agar item spesifik yang sedang promo langsung terekam dengan HPP murah.
  • Diskon Global: AI dilarang mengubah unit_price, dan HANYA bertugas mengubah total_amount menjadi Grand Total akhir. Algoritma proporsional backend akan mengambil alih perhitungannya.

Menindaklanjuti keluhan terkait sistem yang kadang membaca tanggal mundur satu tahun saat input nota kosong (atau mencantumkan “hari ini”), kami telah melakukan perbaikan di layanan mcp-server pada handleParseTransactionTool.

  • Akar Masalah: Konteks waktu (today_date) sebelumnya hanya dikirimkan ke model AI jika sections gambar/teks lebih dari nol. Akibatnya, pada beberapa skenario, AI kehilangan pijakan waktu.
  • Resolusi: Memastikan variabel today_date dan time_context secara absolut disuntikkan ke prompt utama, memaksa AI untuk selalu mematuhi tahun berjalan. (Sudah di-deploy).

Kami juga telah merampungkan UI dan integrasi endpoint untuk fitur cetak pratinjau (PDF) daftar jurnal akuntansi:

  • Backend (sajen): Menambahkan layout laporan journal_list menggunakan library FPDF di dalam endpoint /reports/journals/pdf, mendukung rentang tanggal filter yang dinamis.
  • Frontend (blonjo-ui): Menambahkan JournalReport.tsx di menu Accounting Reports, dengan tata letak laporan yang interaktif dan clean (seperti Buku Besar / General Ledger). Rute /reports/journals telah dikaitkan. (Sudah di-deploy).

  • Evaluasi dan verifikasi kalkulasi Diskon Global & HPP sesuai best practice akuntansi (PSAK 14).
  • Perbaiki bug deteksi tanggal SmartNote yang mundur ke tahun sebelumnya.
  • Implementasi endpoint PDF Laporan Jurnal.
  • Implementasi UI pratinjau Laporan Jurnal di frontend.
  • Verifikasi re-upload template Nota Kasir Yoko dan Indogrosir untuk sinkronisasi template vision OpenCV.