03 β Desain Database Baru (PostgreSQL)¶
Dibaca oleh: Database Engineer & Backend Engineer. Tujuan: skema PostgreSQL yang rapi, bernama jelas, dan tetap mempertahankan model metadata-driven + trigger-first yang sudah terbukti.
Prinsip utama: kita merapikan, bukan merancang ulang bisnis. FIFO tetap FIFO,
jurnal tetap double-entry, jenis_transaksi (eks datakode) tetap jadi mesin
konfigurasi. Yang berubah: nama, tipe data, integritas, dan kerapian.
1. Prinsip Desain¶
- Nama lengkap & jelas, Bahasa Indonesia konsisten.
barang, bukanbrg.transaksi_detail, bukand. Tabel/kolom harus terbaca tanpa kamus. snake_case, huruf kecil semua. Tanpa tanda kutip identifier.- ID otomatis & aman:
BIGINT GENERATED ALWAYS AS IDENTITY. Tidak ada lagimax(id)+1manual. - Integritas ditegakkan DB:
FOREIGN KEY,NOT NULL,CHECK,UNIQUEdi mana relevan. DB menolak data salah, bukan berharap aplikasi sopan. - Tipe data benar: uang
numeric(18,2), booleanboolean, waktutimestamptz, tekstext, kode akunint(bukandecimal; nilaikode Γ 100:110.01 β 11001). - Audit seragam: setiap tabel inti punya
created_at,updated_at,created_by,updated_by. - Schema per tenant di database
erp: data operasional berada ditenant_<kode>, data lintas-tenant dipusat, logika bersama dilogika, dan audit dilogs. - Trigger-first dipertahankan, tetapi setiap trigger/fungsi wajib punya
COMMENT ON+ entri di Dokumen 05. - Tanpa SQL-sebagai-data. Tidak ada lagi
sql_biaya/master/detailstring. Logika jadi fungsi PL/pgSQL bernama. - **JSONB hanya untuk atribut fleksibel/jarang/snapshot β bukan data ** Tulang punggung akuntansi & stok tetap relasional ketat. Lihat Β§7.
2. Konvensi Penamaan (ringkas β detail di Dokumen 07)¶
| Objek | Pola | Contoh |
|---|---|---|
| Tabel | kata benda tunggal, snake_case |
barang, transaksi_detail |
| Kolom PK | id |
id |
| Kolom FK | <entitas>_id |
kontak_id, lokasi_id |
| Kolom boolean | is_ / kata sifat jelas |
is_aktif, pengaruh_stok |
| Kolom uang | tanpa awalan, numeric(18,2) |
total, hpp, dibayar |
| Kolom waktu | _at timestamptz |
created_at, tanggal |
| Index | ix_<tabel>_<kolom> |
ix_transaksi_tanggal |
| FK constraint | fk_<tabel>_<entitas> |
fk_transaksi_detail_barang |
| Trigger | trg_<tabel>_<aksi> |
trg_transaksi_detail_ai |
| Fungsi | kata kerja _ |
hitung_stok, ambil_hpp_fifo |
3. Pemetaan Tipe Data MySQL/MariaDB β PostgreSQL¶
| Lama (MariaDB) | Baru (PostgreSQL) | Catatan |
|---|---|---|
bigint(20) |
bigint |
lebar angka tidak relevan di PG |
int(11) |
integer |
|
tinyint(1) |
boolean |
nilai 0/1 β false/true (transformasi ETL) |
decimal(12,2) (uang) |
numeric(18,2) |
dilebarkan, hindari overflow |
double |
double precision |
hanya non-uang; uang TIDAK pakai double |
varchar(n) |
varchar(n) atau text |
jika n besar/tak jelas β text |
longtext |
text |
|
char(3) (kode) |
varchar(3) |
|
decimal(12,2) (kode akun) |
int |
kode Γ 100: 110.01 β 11001 |
timestamp ... ON UPDATE |
timestamptz + trigger set_updated_at |
PG tak punya ON UPDATE bawaan |
timestamp DEFAULT current_timestamp() |
timestamptz DEFAULT now() |
|
date |
date |
|
| PK tanpa AUTO_INCREMENT | bigint GENERATED ALWAYS AS IDENTITY |
hilangkan getidtrans() manual |
ENGINE=InnoDB latin1 |
(default PG, UTF8) |
charset bersih |
ON DUPLICATE KEY UPDATE |
INSERT ... ON CONFLICT ... DO UPDATE |
dipakai di port prosedur |
IF(c,a,b) |
CASE WHEN c THEN a ELSE b END |
di port PL/pgSQL |
SIGNAL SQLSTATE |
RAISE EXCEPTION |
error bisnis |
variabel sesi @x |
variabel lokal DECLARE |
aman untuk connection pool |
4. Skema Inti β DDL Acuan¶
Ini acuan desain, bukan file final. File final = skrip migrasi bernomor (lihat Dokumen 07). DDL di sini sengaja disederhanakan agar mudah dibaca tim; kolom jarang-pakai dari
t/ddimasukkan bertahap setelah inventaris kolom selesai.
4.1 jenis_transaksi (eks datakode) β mesin konfigurasi¶
CREATE TABLE jenis_transaksi (
kode varchar(3) PRIMARY KEY, -- 'PJ','PB','RJ',...
nama varchar(50) NOT NULL,
deskripsi varchar(150),
kategori varchar(15), -- TRANSAKSI/KEUANGAN/OPNAME/...
pengaruh_stok boolean NOT NULL DEFAULT false, -- eks hitungstok
barang_di_debit boolean NOT NULL DEFAULT false, -- eks barangdebit (arah mutasi)
kas_di_debit boolean NOT NULL DEFAULT false, -- eks kasdebit
hitung_hpp boolean NOT NULL DEFAULT false, -- eks hpp
hpp_dari_beli boolean NOT NULL DEFAULT false, -- eks hppbeli
akun_utama_kode int REFERENCES akun(kode),
akun_kredit_kode int REFERENCES akun(kode),
akun_diskon_kode int REFERENCES akun(kode),
akun_biaya_kode int REFERENCES akun(kode),
akun_hpp_kode int REFERENCES akun(kode),
akun_pembulatan_kode int REFERENCES akun(kode),
urut integer,
is_sistem boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
COMMENT ON TABLE jenis_transaksi IS
'Master & konfigurasi perilaku akuntansi/stok per jenis transaksi (eks datakode).';
4.2 akun (eks rekening) β Bagan Akun¶
Hierarki tiga level: akun_klas (1β9) β akun_subklas (110β800) β akun (11001, 12000, β¦).
Sumber: tabel legacy klas, subklas, rekening. Normalisasi kode: rekening.kode decimal(12,2) Γ 100 β int.
CREATE TABLE akun_klas (
id int PRIMARY KEY, -- 1..9 (= noklasifikasi legacy)
nama varchar(80) NOT NULL
);
CREATE TABLE akun_subklas (
id int PRIMARY KEY, -- 110..800 (= nosubklasifikasi legacy)
klas_id int NOT NULL REFERENCES akun_klas(id),
nama varchar(80) NOT NULL,
saldo_normal char(1) NOT NULL CHECK (saldo_normal IN ('D','K')) -- dari klas.dk
);
CREATE TABLE akun (
kode int PRIMARY KEY, -- kode legacy Γ 100: 110.01 β 11001
subklas_id int REFERENCES akun_subklas(id),
nama varchar(80) NOT NULL, -- eks rekening.akun
is_aktif boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
4.3 barang (eks brg)¶
CREATE TABLE barang (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kode varchar(20) NOT NULL UNIQUE,
nama varchar(100) NOT NULL,
barcode varchar(30),
golongan_id bigint NOT NULL REFERENCES barang_golongan(id),
kategori_id bigint REFERENCES barang_kategori(id),
jenis_id bigint REFERENCES barang_jenis(id),
merk_id bigint REFERENCES barang_merk(id),
satuan_default_id bigint REFERENCES satuan(id),
tanpa_stok boolean NOT NULL DEFAULT false, -- eks nostok (jasa/non-stok)
is_aktif boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
4.4 kontak (eks ktk)¶
CREATE TABLE kontak (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kode varchar(20) NOT NULL UNIQUE,
nama varchar(100) NOT NULL,
is_pelanggan boolean NOT NULL DEFAULT false, -- eks flag c
is_supplier boolean NOT NULL DEFAULT false, -- eks flag s
is_pegawai boolean NOT NULL DEFAULT false, -- eks flag p
perusahaan varchar(80),
is_aktif boolean NOT NULL DEFAULT true, -- eks aktif=1 (semua flag lama jadi boolean)
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
Peran kontak NON-eksklusif (penting). Di sistem lama:
ktk.c=customer,ktk.s=supplier,ktk.p=pegawai. Satu kontak bisa punya lebih dari satu peran sekaligus (mis. seorang pegawai yang juga supplier). Karena itu peran dimodelkan sebagai tiga boolean terpisah (is_pelanggan/is_supplier/is_pegawai), bukan satu kolom "tipe" tunggal. Semua flag angka lama (aktif,c,s,p, dst.) βbooleaneksplisit (utang S4/K6 Dokumen 01).
4.5 transaksi (eks t) β header universal¶
CREATE TABLE transaksi (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
nomor_transaksi varchar(40) NOT NULL UNIQUE, -- jenis.nomor_bukti (TANPA kdpc)
nomor_transaksi_lama varchar(40), -- notrans lama (kdpc.*) utk telusur histori
jenis_kode varchar(3) NOT NULL REFERENCES jenis_transaksi(kode),
nomor_bukti bigint NOT NULL,
sumber_perangkat varchar(10), -- METADATA: desktop/mobile/web (eks kdpc, BUKAN kunci)
tanggal timestamptz NOT NULL DEFAULT now(),
kontak_id bigint REFERENCES kontak(id),
pegawai_id bigint REFERENCES kontak(id),
lokasi_id bigint REFERENCES lokasi(id),
lokasi_tujuan_id bigint REFERENCES lokasi(id), -- utk pemindahan (PL)
cabang_id bigint REFERENCES cabang(id), -- eks divisi_id (devisiβcabang, Dokumen 08 Β§5)
subtotal numeric(18,2) NOT NULL DEFAULT 0,
diskon numeric(18,2) NOT NULL DEFAULT 0,
biaya numeric(18,2) NOT NULL DEFAULT 0,
pembulatan numeric(18,2) NOT NULL DEFAULT 0,
total numeric(18,2) NOT NULL DEFAULT 0,
dibayar numeric(18,2) NOT NULL DEFAULT 0,
sisa_kredit numeric(18,2) NOT NULL DEFAULT 0,
total_pelunasan numeric(18,2) NOT NULL DEFAULT 0,
hpp_total numeric(18,2) NOT NULL DEFAULT 0,
qty_total numeric(18,2) NOT NULL DEFAULT 0,
jatuh_tempo date,
termin_kode varchar(20) REFERENCES termin(kode),
akun_kode int REFERENCES akun(kode),
akun_kas_kode int REFERENCES akun(kode),
akun_kredit_kode int REFERENCES akun(kode),
harga_tingkat_kode varchar(20),
status smallint NOT NULL DEFAULT 0, -- status eksplisit
keterangan varchar(150),
catatan text,
dibuat_oleh varchar(40) NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (jenis_kode, nomor_bukti) -- tenant = 1 DB, jadi tak perlu kdpc
);
CREATE INDEX ix_transaksi_tanggal ON transaksi (tanggal);
CREATE INDEX ix_transaksi_jenis ON transaksi (jenis_kode);
CREATE INDEX ix_transaksi_kontak ON transaksi (kontak_id);
Catatan: kolom
cek(sinyal kontrol trigger lama) dihapus. Perilaku khusus yang dulu diatur lewatcek=56/66/...diganti dengan kolomstatus/asal_proseseksplisit + parameter fungsi (lihat Dokumen 05 Β§kontrol alur).
4.6 transaksi_detail (eks d)¶
CREATE TABLE transaksi_detail (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
transaksi_id bigint NOT NULL REFERENCES transaksi(id) ON DELETE CASCADE,
urut integer NOT NULL,
barang_id bigint NOT NULL REFERENCES barang(id),
barang_kode varchar(20), -- snapshot saat transaksi
qty_nota numeric(14,2) NOT NULL,
satuan_id bigint REFERENCES satuan(id),
isi_konversi numeric(14,2) NOT NULL DEFAULT 1,
qty numeric(14,2) NOT NULL, -- qty_nota * isi_konversi
mutasi_stok numeric(14,2) NOT NULL DEFAULT 0, -- Β± qty (eks 'mutasi')
harga numeric(18,2) NOT NULL DEFAULT 0,
hpp numeric(18,2) NOT NULL DEFAULT 0,
diskon numeric(18,2) NOT NULL DEFAULT 0,
diskon_nota numeric(18,2) NOT NULL DEFAULT 0,
jumlah numeric(18,2) NOT NULL DEFAULT 0, -- harga * qty_nota
total numeric(18,2) NOT NULL DEFAULT 0, -- jumlah - diskon
lokasi_id bigint REFERENCES lokasi(id),
lokasi_tujuan_id bigint REFERENCES lokasi(id),
ref_detail_id bigint REFERENCES transaksi_detail(id), -- retur/pelunasan
ref_transaksi_id bigint REFERENCES transaksi(id),
poin numeric(14,2) NOT NULL DEFAULT 0,
keterangan varchar(150),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (transaksi_id, urut)
);
CREATE INDEX ix_trxd_transaksi ON transaksi_detail (transaksi_id);
CREATE INDEX ix_trxd_barang ON transaksi_detail (barang_id);
4.6A transaksi_draft β area kerja sebelum dokumen final¶
Fitur baru pasca migrasi legacy memakai pola:
transaksi_draft -> transaksi
transaksi_draft_detail -> transaksi_detail
Tujuannya agar dokumen yang masih berubah-ubah tidak langsung menyentuh
transaksi_detail, karena transaksi_detail sudah menjadi sumber trigger stok,
FIFO, HPP, dan jurnal.
Aturan desain:
transaksi_draftdantransaksi_draft_detailadalah tenant-local.- Tidak ada trigger stok/FIFO/jurnal pada
transaksi_draft_detail. - Semua jenis draft tetap memakai
jenis_kodedarijenis_transaksi. - Draft bisa mewakili order penjualan, order pembelian, permintaan mutasi, perintah produksi, bahan produksi, dan hasil produksi.
- Realisasi draft ke transaksi final dicatat di
transaksi_draft_relasidantransaksi_draft_detail_relasi. - Relasi antar transaksi final tetap memakai kolom referensi existing seperti
ref_transaksi_iddanref_detail_id, bukan tabel relasi baru.
Migrasi fitur ini tidak mengambil data dari legacy dan direncanakan sebagai seri akhir:
V900__tracking_draft_transaksi.sql
V901__tracking_relasi_draft_transaksi.sql
V902__tracking_produksi_mvp.sql
V903__tracking_jenis_transaksi_seed.sql
V904__tracking_view_operasional.sql
DDL rinci dan checklist review ada di:
database/fitur/TRACKING-DDL.mdβ DDL tabel draft & relasidatabase/fitur/TRACKING-PRODUKSI.mdβ DDL BOM & work centerdatabase/fitur/TODO.md
4.7 Stok realtime stok (eks s) β cache cepat + jangkar rekonsiliasi¶
Pola pemilik (disengaja, dipertahankan): stok per lokasi = tampilan cepat &
realtime (dijaga trigger); sumber kebenaran tetap SUM(mutasi_stok); selisih
antara keduanya = alat audit untuk menemukan transaksi/logika yang salah.
CREATE TABLE stok ( -- eks `s` (per lokasi)
barang_id bigint NOT NULL REFERENCES barang(id),
lokasi_id bigint NOT NULL REFERENCES lokasi(id),
qty numeric(18,2) NOT NULL DEFAULT 0,
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (barang_id, lokasi_id)
);
COMMENT ON TABLE stok IS
'Cache stok per lokasi = SUM(transaksi_detail.mutasi_stok). Sumber kebenaran tetap detail.';
-- Total stok per barang (eks brg.stok) diekspos lewat v_barang.stok_total.
-- View = selalu benar, tak bisa melenceng.
CREATE VIEW v_barang AS
SELECT b.id, b.kode, b.nama, COALESCE(s.qty_total, 0) AS stok_total
FROM barang b
LEFT JOIN (SELECT barang_id, SUM(qty) AS qty_total
FROM stok GROUP BY barang_id) s ON s.barang_id = b.id;
-- ALAT AUDIT RESMI: bandingkan cache vs hitung-ulang dari sumber.
-- Baris yang muncul di sini = ada yang salah, langsung ditelusuri.
CREATE VIEW v_stok_selisih AS
SELECT s.barang_id, s.lokasi_id,
s.qty AS qty_cache,
COALESCE(h.qty_benar, 0) AS qty_seharusnya,
s.qty - COALESCE(h.qty_benar, 0) AS selisih
FROM stok s
LEFT JOIN LATERAL (
SELECT SUM(d.mutasi_stok) AS qty_benar
FROM transaksi_detail d
JOIN transaksi t ON t.id = d.transaksi_id
WHERE d.barang_id = s.barang_id
AND (t.lokasi_id = s.lokasi_id OR t.lokasi_tujuan_id = s.lokasi_id)
AND t.jenis_kode <> 'GP'
) h ON true
WHERE s.qty <> COALESCE(h.qty_benar, 0);
brg.stoklama (total keseluruhan) diganti view tenantv_barang.stok_totalβ tidak bisa "melenceng" karena dihitung saat dibaca. Jika butuh kecepatan ekstra, boleh jadi kolom cachebarang.stok_totalyang dijaga trigger (pola lama), dengan konsekuensi harus ikut direkonsiliasi. Default: pakai view.
4.8 FIFO untuk HPP¶
CREATE TABLE fifo_lapisan ( -- eks fifo
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
detail_masuk_id bigint NOT NULL REFERENCES transaksi_detail(id),
transaksi_id bigint NOT NULL REFERENCES transaksi(id),
barang_id bigint NOT NULL REFERENCES barang(id),
qty_masuk numeric(18,2) NOT NULL,
qty_keluar numeric(18,2) NOT NULL DEFAULT 0,
qty_sisa numeric(18,2) NOT NULL,
harga numeric(18,2) NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ix_fifo_barang_sisa ON fifo_lapisan (barang_id) WHERE qty_sisa > 0;
CREATE TABLE fifo_konsumsi ( -- eks fifokeluar
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
lapisan_id bigint NOT NULL REFERENCES fifo_lapisan(id),
detail_keluar_id bigint NOT NULL REFERENCES transaksi_detail(id),
transaksi_id bigint NOT NULL REFERENCES transaksi(id),
barang_id bigint NOT NULL REFERENCES barang(id),
qty numeric(18,2) NOT NULL
);
4.9 Jurnal jurnal (eks j) β sekarang PUNYA ID & integritas¶
CREATE TABLE jurnal (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
transaksi_id bigint NOT NULL REFERENCES transaksi(id) ON DELETE CASCADE,
nomor_transaksi varchar(40) NOT NULL,
sumber varchar(20) NOT NULL, -- eks 'data' (TR/TRKREDIT/...)
akun_kode int NOT NULL REFERENCES akun(kode),
uraian varchar(150),
debit numeric(18,2) NOT NULL DEFAULT 0,
kredit numeric(18,2) NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
CHECK (debit >= 0 AND kredit >= 0),
CHECK (NOT (debit > 0 AND kredit > 0)) -- satu baris satu sisi
);
CREATE INDEX ix_jurnal_transaksi ON jurnal (transaksi_id);
CREATE INDEX ix_jurnal_akun ON jurnal (akun_kode);
Tambahan integritas yang dulu tidak ada: uji keseimbangan. Sediakan view
v_jurnal_tidak_imbang(miripcek_jurnal_balancelama) + uji otomatis di migrasi: setiap transaksi harusSUM(debit) = SUM(kredit).
5. Yang Berubah dari Sistem Lama (ringkas, untuk tim)¶
| Aspek | Lama | Baru |
|---|---|---|
| Penamaan | t,d,brg,ktk |
transaksi,transaksi_detail,barang,kontak |
| ID | max(id)+1 manual |
GENERATED ALWAYS AS IDENTITY |
| PK detail | string idtrans.idbarang |
bigint identity + UNIQUE(transaksi_id, urut) |
| Kode akun | decimal(12,2) |
int (kode Γ 100: 110.01 β 11001) |
| Boolean | tinyint(1) campur |
boolean tegas |
Sinyal cek |
angka ajaib 56/66/7 | kolom status eksplisit |
| SQL-as-data | sql_biaya,master,detail |
fungsi PL/pgSQL bernama |
| Charset | latin1 | UTF8 |
| Jurnal | tanpa PK | PK + FK + CHECK keseimbangan |
| Penomoran | kdpc (kode perangkat) di nomor |
sequence server; kdpcβmetadata sumber_perangkat (Dok 08 Β§1) |
| Bagan Akun | per tenant, kode beda-beda | master standar di pusat, salinan lokal (Dok 08 Β§2) |
devisi |
multi-perusahaan via iddevisi (hardcode) |
cabang (sub-entitas dalam tenant) (Dok 08 Β§5) |
| Stok total | kolom cache brg.stok |
view tenant v_stok + v_stok_selisih (audit) |
| Login | 2 pintu (opr desktop, inet mobile) |
1 identitas pusat.pengguna, 1 API (Dok 08 Β§3) |
| Organisasi | 120 tabel + 63 view tercampur tanpa pola fisik yang jelas | schema pusat/logika/logs + tenant_<kode> |
6. Yang TIDAK Berubah (jaga jangan rusak)¶
- Model 1 tabel transaksi untuk semua jenis (
transaksi/transaksi_detail). jenis_transaksi(datakode) sebagai mesin pengatur akuntansi & stok.- Trigger-first: stok, HPP FIFO, jurnal tetap dihitung otomatis oleh DB.
- Logika bisnis FIFO & double-entry β dipindah apa adanya (Dokumen 05).
7. Kapan Pakai JSONB (dan Kapan Jangan)¶
Pemilik tertarik pada konsep "menyimpan JSON di field" tapi belum yakin untuk apa. Ini jawabannya. PostgreSQL punya tipe
jsonb(JSON ter-indeks, bisa di-query). Berguna bila dipakai di tempat yang tepat, berbahaya bila salah.
β Pakai JSONB untuk: atribut fleksibel / jarang / snapshot¶
| Kasus | Contoh kolom |
|---|---|
| Spesifikasi yang beda per jenis barang (oli: viskositas; ban: ukuran; jasa: kosong) | barang.atribut jsonb (pengganti brgspek yang kaku) |
| Pengaturan/konfigurasi per tenant/cabang/pengguna | cabang.pengaturan jsonb, atau tabel konfigurasi tenant bila diperlukan |
| Snapshot/jejak audit (rekam kondisi data saat berubah) | logs.audit.data_lama jsonb, data_baru jsonb |
| Payload mentah integrasi luar (respons API, webhook) sebelum diproses | tabel integrasi khusus dengan kolom payload jsonb |
| Metadata lepas yang tak pernah jadi syarat JOIN/SUM akuntansi | *.meta jsonb |
JSONB di PostgreSQL bisa di-index (GIN) β pencarian atribut tetap cepat. Cocok untuk data yang bentuknya tak seragam dan tak ingin bikin puluhan kolom NULL.
β JANGAN pakai JSONB untuk: data inti yang dihitung/direlasi/diaudit¶
- Uang, qty, hpp, saldo, debit/kredit, jurnal β wajib kolom
numericdenganFK/CHECK. JANGAN simpan total/HPP di dalam JSON. - Apa pun yang menjadi FOREIGN KEY atau sering di-
WHERE/JOIN/SUM/GROUP BY(akun, barang_id, kontak_id, tanggal, jenis_kode). - Pengganti normalisasi karena malas bikin tabel. JSON bukan alasan menghindari desain relasional. Kalau datanya berstruktur & dipakai relasi β tabel, bukan JSON.
Aturan praktis untuk ERP ini¶
Tulang punggung akuntansi & stok = relasional ketat (FK +
numeric+CHECK). JSONB = lemari serbaguna untuk atribut variatif/jarang/snapshot. JSONB melengkapi skema, bukan menggantikannya.
Tegas: transaksi, transaksi_detail, jurnal, fifo_lapisan, fifo_konsumsi,
stok, akun tetap kolom relasional penuh β tanpa JSON. JSONB boleh muncul
di barang.atribut, *.pengaturan, *.meta, dan tabel audit/integrasi.
Langkah berikut: Dokumen 04 (multi-tenant) lalu Dokumen 05 (port trigger). DDL di sini difinalkan menjadi skrip migrasi
V001__template_tenant.sqldst. (Dokumen 07).