Chapter 4 β€” Development Conventions (v2 β€” Final)

Status: πŸ”’ Mengikat Β· Rumah: 1_BMP_Blueprint Pasangan: Data Model v1.5 (skema) Β· Blueprint Ch.3 (arsitektur) Β· UI Reference (pola)


4.0 Prinsip

  1. Konvensi dipaksakan tooling, bukan ingatan β€” compiler, Credo, Dialyzer, CI. Yang tak bisa dicek otomatis, ditulis sebagai doctest.
  2. Zero custom libraries β€” logika bisnis hidup di Ash domain; stack tunduk hierarki dependency Chapter 3 Β§10.
  3. Dokumentasi = test β€” setiap @doc aksi publik wajib punya doctest; doc yang tidak berjalan adalah doc yang berbohong.
  4. Satu aturan, satu rumah β€” pola UI di UI Reference; skema di Data Model; arsitektur di Blueprint. Komentar kode merujuk dokumen, tidak menduplikasi.
  5. Keamanan tiga lapis, tiga rumah (v2) β€” Cloak (at-rest) + Ash.Policy.Authorizer (aksi) + konfigurasi workspace (visibilitas UI). Hide UI β‰  security.
  6. Ledger immutabel = source of truth (v2) β€” derived state (bins, closing) selalu rebuildable; tidak ada write langsung.

4.1 Coding Standard & Naming

Aspek Konvensi
Format & static analysis mix format wajib; Credo --strict clean; Dialyzer clean (wajib di boundary SDK)
Domain Bmp.<Module> = satu bounded context (Bmp.Sales, Bmp.Inventory, Bmp.Accounting, …); folder per-domain, bukan per-layer teknis
Resource Bmp.<Module>.<Entity> (mis. Bmp.Sales.SalesOrder); aksi = kata kerja (:create_draft, :submit, :cancel)
Event Bmp.<Module>.Events.<Entity><PastTense> (mis. SalesOrderSubmitted); dipublish via Ash.Notifier
Report Bmp.<Module>.Reports.<Name> β€” fungsi proyeksi / read action murni; tanpa state tersimpan kecuali snapshot materialized (bins, closing)
LiveView BmpWeb.<Module>.<Page>Live; wajib memakai komponen bersama (<.button>, <.toast>, <.modal>, <.empty_state>, <.pager>)
Error Ash.Error custom dengan pesan bisa ditindaklanjuti ("Sisa budget Rp X", bukan "Invalid input")
Bahasa Lihat Β§4.9 β€” satu bahasa English untuk sistem; tanpa i18n; output fiskal Bahasa Indonesia (Β§4.13)

4.2 Struktur Folder per-Domain (diisi v2)

lib/bmp/<domain>/            # satu folder per bounded context
  resources/  actions/  policies/  events/  reports/
lib/bmp_web/components/      # komponen bersama (kontrak compile-time)
lib/bmp_web/<domain>/        # LiveView per modul β€” TIPIS, tanpa logika bisnis
priv/repo/migrations/        # migrasi Ash + bump schema_version
priv/static/fonts/           # Inter .woff2 (bundle lokal)
priv/print_templates/        # template XML Tincture per dokumen
priv/print_templates/fiscal/ # template fiskal Bahasa Indonesia (Β§4.13)
agent/                       # crate Rust sync-agent β€” DI LUAR app Ash

Aturan:

  1. Folder domain = satu-satunya rumah logika bisnis; bmp_web hanya presentasi.
  2. Lintas domain = public actions + events, tidak pernah import resource domain lain langsung.
  3. agent/ tidak boleh memanggil aksi Ash; mutasi replikasi aman di level constraint (Invarian #6).

4.3 Generator (mix task)

  1. mix bmp.gen.resource Domain Name --archival --cloak=f1,f2 --ledger
  2. mix bmp.gen.report Domain Name Β· mix bmp.gen.event Domain Name
  3. Output generator: UUID pk, timestamps, stub policy, stub doctest, migrasi dengan bump schema_version. Generator adalah penjaga konvensi pertama.

4.4 Testing Strategy & CI/CD

Lapis Tool
Doctest default semua aksi publik (dokumentasi = test)
Unit/seed Ash.Generator
Property StreamData untuk invarian: Ξ£debit = Ξ£credit; qty >= 0; SLE/GL append-only; Neraca seimbang (TOTAL ASET = TOTAL LIABILITAS + EKUITAS)
Kontrak internal Mox
HTTP eksternal Bypass
Async jobs Oban.Testing
Sync idempotensi (v2) changeset sama diterapkan dua kali β†’ yang kedua OMIT
Template fiskal (v2) smoke-render semua template per build; gagal bila placeholder tak terisi atau string di luar Glosarium Fiskal
CI format --check-format β†’ credo --strict β†’ dialyzer β†’ test (SQLite tunggal, tanpa matrix lintas-DB) β†’ cek boundary SDK

Debug: LiveDebugger (dev-only) + Phoenix.LiveView.Debug (production, read-only).

4.5 Migration Strategy & Versioning

  1. Migrasi hanya via Ash; tanpa SQL manual di production.
  2. Setiap migrasi menaikkan schema_version (kontrak sync-agent); agent menolak koneksi bila versi node tidak cocok.
  3. Urutan upgrade: server kantor dulu, baru laptop β€” sync berhenti sementara, tidak corrupt.
  4. Non-destruktif untuk tabel finansial: tambah kolom nullable dulu; "hapus" = AshArchival, tidak pernah DROP data (retensi 10 tahun).
  5. Tanpa DDL runtime: dimensi akuntansi custom = kolom terdefinisi kode per instalasi, terversioning (menolak "unlimited" ERPNext).
  6. Business rule kritikal sebagai CHECK constraint SQLite (qty >= 0, amount > 0) β€” last line of defense replikasi (Invarian #6).
  7. Pragma wajib per node: journal_mode=WAL; busy_timeout=5000 (Addendum Chapter 3).
  8. Versioning app = SemVer; changelog ditulis sebagai dokumentasi.

4.6 Release

  1. Single binary (Burrito); font, ikon (Lucide SVG), dan library pure-BEAM (tincture, eqrcode, exceed, nimble_csv) ter-bundle β€” nol CDN, nol binary eksternal.

4.7 Extension SDK

  1. Permukaan publik = hanya public actions + events per domain. Modul komunitas (Chapter 8) menumpang lewat situ, tidak pernah write langsung ke resource core.
  2. Boundary dipaksakan di CI: Dialyzer + Credo custom check (SDK compliance).

4.8 Documentation Standard

  1. @moduledoc setiap resource memuat: mesin apa, rujukan silang (Data Model Β§x, UI Reference Β§y), invarian.
  2. Log keputusan per chapter (pola v9 Β§9): siapa/kapan/kenapa.
  3. Penanda status konsisten dengan Peta Menu & Data Model: πŸ”’ terkunci Β· πŸ‘οΈ hide Β· ❌ buang.

4.9 Bahasa & Lokalisasi (baru v2)

  1. Satu bahasa untuk sistem: English. Kode, identifier, komentar, string UI, serta istilah teknis & proper noun (Ash, Phoenix, Oban, Tincture, Sales Order, Delivery Note, Work Order, Batch, Submit, Settings, Background Job, Error Log, dll.) tidak diterjemahkan. Ash tetap Ash, bukan "Abu".
  2. Tanpa lapisan i18n/Gettext. Satu istilah, satu arti, di semua tempat: developer membaca Bmp.Sales.SalesOrder, user melihat "Sales Order", dokumen menulis "Sales Order".
  3. Bahasa Indonesia hanya untuk prosa naratif β€” dokumen alasan (BMP-Kenapa.md), pesan bantuan panjang, komunikasi internal β€” dengan terminologi tetap English.
  4. Pengecualian karena undang-undang: output fiskal untuk otoritas wajib Bahasa Indonesia β€” lihat Β§4.13.
  5. Rationale: terjemahan menghilangkan distingsi (English punya Brown & Chocolate; Indonesia hanya "coklat"), dan tanpa glossary terjemahan mesin melahirkan "Kopi" untuk Copy β€” menu warung, bukan editor.

4.10 Uang, Presisi & Waktu (baru v2)

  1. Uang via AshMoney; tampilan & total dokumen = Rupiah bulat (tanpa desimal); rate satuan internal s.d. 4 desimal; selisih pembulatan β†’ akun Round Off.
  2. conversion_factor = 9 desimal (konversi UOM).
  3. Waktu disimpan UTC; posting_date (date) + posting_time (time); UI render Asia/Jakarta; tidak ada timezone per-user untuk tanggal fiskal.

4.11 ID, Numbering Series & Event Payload (baru v2)

  1. UUID pk di semua tabel; UUIDv7 untuk tabel ledger/transaksi (time-ordered, lokalitas index SQLite).
  2. Numbering series terdefinisi kode per instalasi (tanpa DDL runtime): SO-SHOPEE-2026-W32, BLG-2026-001, dll. Dokumen cancel menyimpan nomornya (tidak dipakai ulang).
  3. Payload event membawa ID + fakta immutabel minimal; konsumen yang butuh data lengkap membaca via public action (tanpa salinan basi di payload).

4.12 Error, Logging & Async Jobs (baru v2)

  1. Taksonomi error: validation (inline) Β· domain (toast actionable) Β· infra (System Console + Sentry).
  2. Telemetry event bernama bmp.<domain>.<entity>.<action>.
  3. Log sanitizer: field ter-Cloak (margin/COGS, payroll, bank, resep, PII) tidak pernah muncul di log/telemetry/Sentry.
  4. Async jobs (Oban/Broadway/AshOban sesuai Ch.3 Β§2): semua job idempoten; job membawa ID dan membaca data terkini saat run; scheduled job terdaftar di satu manifest dan tampil di System Console.

4.13 Laporan Fiskal & Template Output Bahasa Indonesia (baru v2)

Aturan:

  1. Output fiskal = laporan wajib Bahasa Indonesia menurut UU KUP Pasal 28 ayat (2) & lampiran SPT Badan: Laporan Posisi Keuangan (Neraca), Laporan Laba Rugi, Laporan Arus Kas, Neraca Saldo, Buku Besar, Bukti Potong PPh. Faktur Pajak mengikuti format standar DJP (BMP hanya memasok data).
  2. Semua output fiskal dirender dari template Tincture di priv/print_templates/fiscal/*.xml β€” terversioning, tanpa eksekusi kode; perubahan template = perubahan dokumen (wajib changelog).
  3. Seluruh string template diambil hanya dari Glosarium Fiskal (bawah). Tidak ada string Indonesia hard-coded di kode; CI mengeceknya (Β§4.4).
  4. CoA account_name wajib diisi Bahasa Indonesia ("Beban Marketplace Fee"); account_number = kode angka. Nama internal/kode tetap English.
  5. Angka: Rupiah penuh, bulat, pemisah ribuan titik; kepala memuat "(Dalam Rupiah penuh, kecuali dinyatakan lain)".
  6. Export xlsx lampiran SPT (via exceed) memakai header kolom yang sama persis dengan template print β€” satu glosarium, dua wujud.
  7. CI: smoke-render semua template fiskal per build; gagal bila placeholder tak terisi atau string di luar glosarium.

Kepala & kaki bersama (semua template):

{NAMA_PERUSAHAAN}
{ALAMAT_LENGKAP}
NPWP : {NPWP}
──────────────────────────────────────────────
{JUDUL_LAPORAN}   ← uppercase, dari glosarium
{PERIODE}         ← "Per 31 Desember 2026" / "Untuk Tahun yang Berakhir 31 Desember 2026"
(Dalam Rupiah penuh, kecuali dinyatakan lain)

Kaki: Dicetak dari BMP : {tanggal} {pencetak} Β· Halaman {n} dari {N} Β· blok tanda tangan: Disusun oleh, (______) {Nama – Finance} Β· Mengetahui, (______) {Nama – Owner}

Template 1 β€” LAPORAN POSISI KEUANGAN (NERACA) (invarian: TOTAL ASET = TOTAL LIABILITAS DAN EKUITAS)

ASET
ASET LANCAR
  Kas dan Setara Kas                          {n}
  Piutang Usaha                               {n}
  Persediaan                                  {n}
  Pajak Dibayar di Muka                       {n}
TOTAL ASET LANCAR                             {n}
ASET TIDAK LANCAR
  Aset Tetap                                  {n}
  Akumulasi Penyusutan                       ({n})
TOTAL ASET TIDAK LANCAR                       {n}
TOTAL ASET                                    {n}

LIABILITAS DAN EKUITAS
LIABILITAS JANGKA PENDEK
  Utang Usaha                                 {n}
  Utang Pajak                                 {n}
  Pendapatan Diterima di Muka                 {n}
TOTAL LIABILITAS JANGKA PENDEK                {n}
LIABILITAS JANGKA PANJANG                     {n}
TOTAL LIABILITAS                              {n}
EKUITAS
  Modal Disetor                               {n}
  Laba Ditahan                                {n}
  Laba (Rugi) Berjalan                        {n}
TOTAL EKUITAS                                 {n}
TOTAL LIABILITAS DAN EKUITAS                  {n}

Template 2 β€” LAPORAN LABA RUGI DAN PENGHASILAN KOMPREHENSIF LAIN

PENDAPATAN USAHA                              {n}
BEBAN POKOK PENJUALAN                        ({n})
LABA KOTOR                                    {n}
BEBAN OPERASIONAL
  Beban Marketplace Fee                       {n}
  Beban Komisi Reseller                       {n}
  Beban Diskon Penjualan                      {n}
  Beban Promosi                               {n}
  Beban Gaji dan Kesejahteraan                {n}
  Beban Penyusutan                            {n}
  Beban Umum dan Administrasi                 {n}
TOTAL BEBAN OPERASIONAL                      ({n})
LABA OPERASIONAL                              {n}
PENDAPATAN (BEBAN) LAIN-LAIN                  {n}
LABA SEBELUM PAJAK                            {n}
BEBAN PAJAK PENGHASILAN                      ({n})
LABA BERSIH                                   {n}

Template 3 β€” LAPORAN ARUS KAS (metode tidak langsung) (invarian: KAS AKHIR = saldo akun Bank+Cash di Neraca)

ARUS KAS DARI AKTIVITAS OPERASI
  Laba Bersih                                 {n}
  Penyesuaian: Beban Penyusutan               {n}
  Perubahan Piutang Usaha                    ({n}/{n})
  Perubahan Persediaan                       ({n}/{n})
  Perubahan Utang Usaha & Pajak               {n}
KAS BERSIH DARI OPERASI                       {n}
ARUS KAS DARI AKTIVITAS INVESTASI
  Perolehan Aset Tetap                       ({n})
KAS BERSIH DARI INVESTASI                    ({n})
ARUS KAS DARI AKTIVITAS PENDANAAN
  Setoran Modal                               {n}
KAS BERSIH DARI PENDANAAN                     {n}
KENAIKAN (PENURUNAN) BERSIH KAS               {n}
KAS DAN SETARA KAS AWAL PERIODE               {n}
KAS DAN SETARA KAS AKHIR PERIODE              {n}

Template 4 β€” NERACA SALDO

Kode Akun Nama Akun Debit Kredit
1-1000 Kas dan Setara Kas {n}
… … … …
JUMLAH {Ξ£} {Ξ£}

Template 5 β€” BUKU BESAR (satu akun per blok)

Kepala blok: {KODE} β€” {NAMA AKUN}

Tanggal Keterangan Ref Debit Kredit Saldo
{tgl} {voucher_no + party} {JE/SI/PI/PE} {n} {n} {n}

Template 6 β€” BUKTI POTONG PPh 23 (mengikuti struktur e-Bupot; sumber: tax_withholding_entries + purchase_invoices)

Blok Field
Dokumen Nomor & seri bukti potong Β· Tanggal Β· Masa Pajak & Tahun Pajak
Pemotong Pajak Nama Β· NPWP Β· Alamat
Penerima Penghasilan Nama Β· NPWP Β· Alamat
Transaksi Uraian penghasilan (Jasa / Sewa / Dividen / Royalti) Β· Jumlah Bruto
Perhitungan Tarif (%) Β· PPh yang Dipotong
Pernyataan "Demikian bukti potong ini dibuat dengan sebenarnya" + tanda tangan pemotong

Glosarium Fiskal β€” sumber string tunggal (Β§4.13-G)

Internal (kode/identifier) Label fiskal (Indonesia)
Assets Aset
Current Assets Aset Lancar
Non-current Assets Aset Tidak Lancar
Liabilities Liabilitas
Equity Ekuitas
Cash & Bank Kas dan Setara Kas
Receivables Piutang Usaha
Inventory Persediaan
Fixed Assets Aset Tetap
Accumulated Depreciation Akumulasi Penyusutan
Payables Utang Usaha
Tax Payable Utang Pajak
Paid-in Capital Modal Disetor
Retained Earnings Laba Ditahan
Revenue Pendapatan Usaha
Cost of Goods Sold Beban Pokok Penjualan
Gross Profit Laba Kotor
Operating Profit Laba Operasional
Net Profit Laba Bersih
Trial Balance Neraca Saldo
General Ledger Buku Besar
Cash Flow Arus Kas
Fiscal Year Tahun Pajak
Withholding (PPh 23) Pemotongan Pajak

Changelog v2 (vs docx v1)