Lewati ke isi

Sentra Retail — Database Design

Database menggunakan pendekatan PostgreSQL untuk Server Cloud utama (guna menangani ACID Compliance dan relasi kompleks) dan SQLite pada terminal POS lokal.

4.1 Entity Relationship Diagram (ERD)

erDiagram
    STORES ||--o{ USERS : "has"
    USERS ||--o{ SHIFTS : "runs"
    PRODUCTS ||--o{ PRODUCT_VARIANTS : "has"
    PRODUCT_VARIANTS ||--o{ INVENTORIES : "tracked_in"
    STORES ||--o{ INVENTORIES : "holds"
    TRANSACTIONS ||--o{ TRANSACTION_DETAILS : "contains"
    PRODUCT_VARIANTS ||--o{ TRANSACTION_DETAILS : "sold_in"
    STORES ||--o{ TRANSACTIONS : "records"
    USERS ||--o{ TRANSACTIONS : "handled_by"
    MEMBERS |o--o{ TRANSACTIONS : "applies_to"
    PROMOS ||--o{ TRANSACTION_DETAILS : "triggers"
    SHIFTS ||--o{ TRANSACTIONS : "associates"

4.2 Skema Tabel Relasional DDL (PostgreSQL Dialect)

-- 1. Tabel Stores (Cabang / Gudang)
CREATE TABLE stores (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    address TEXT,
    phone VARCHAR(20),
    type VARCHAR(20) CHECK (type IN ('STORE', 'WAREHOUSE', 'STORE_WITH_WAREHOUSE')),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 2. Tabel Users (Karyawan)
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    store_id UUID REFERENCES stores(id),
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    full_name VARCHAR(100) NOT NULL,
    role VARCHAR(30) CHECK (role IN ('OWNER', 'SYSTEM_ADMIN', 'STORE_MANAGER', 'CASHIER', 'WAREHOUSE_STAFF', 'AUDITOR')),
    pin VARCHAR(6) NOT NULL, -- PIN untuk otorisasi cepat/Void di POS
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 3. Tabel Products
CREATE TABLE products (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    sku VARCHAR(50) UNIQUE NOT NULL,
    barcode VARCHAR(50) UNIQUE,
    name VARCHAR(150) NOT NULL,
    category VARCHAR(50) NOT NULL,
    base_price DECIMAL(15,2) NOT NULL, -- Harga Pokok Pembelian (HPP)
    tax_rate DECIMAL(5,2) DEFAULT 11.00, -- Default PPN 11%
    has_variant BOOLEAN DEFAULT FALSE,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 4. Tabel Product Variants (Mendukung Varian Ukuran/Warna)
CREATE TABLE product_variants (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    product_id UUID REFERENCES products(id) ON DELETE CASCADE,
    variant_sku VARCHAR(50) UNIQUE NOT NULL,
    variant_barcode VARCHAR(50) UNIQUE,
    size VARCHAR(20),
    color VARCHAR(20),
    additional_price DECIMAL(15,2) DEFAULT 0.00,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 5. Tabel Inventories (Stok di Gudang & Cabang)
CREATE TABLE inventories (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    store_id UUID REFERENCES stores(id),
    product_variant_id UUID REFERENCES product_variants(id),
    qty INT NOT NULL DEFAULT 0,
    minimum_stock INT NOT NULL DEFAULT 5,
    batch_number VARCHAR(50), -- Khusus Apotek / Kosmetik
    expired_date DATE,        -- Khusus Apotek / Kosmetik
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(store_id, product_variant_id, batch_number)
);

-- 6. Tabel Members (Loyalty Program)
CREATE TABLE members (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    phone VARCHAR(20) UNIQUE NOT NULL,
    tier VARCHAR(20) CHECK (tier IN ('BRONZE', 'SILVER', 'GOLD', 'PLATINUM')) DEFAULT 'BRONZE',
    points INT DEFAULT 0,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 7. Tabel Shifts
CREATE TABLE shifts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id),
    store_id UUID REFERENCES stores(id),
    start_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    end_time TIMESTAMP WITH TIME ZONE,
    start_cash_amount DECIMAL(15,2) NOT NULL DEFAULT 0.00, -- Uang modal laci kasir
    end_cash_expected DECIMAL(15,2), -- Hitungan sistem
    end_cash_declared DECIMAL(15,2), -- Inputan fisik kasir saat tutup shift
    status VARCHAR(20) CHECK (status IN ('OPEN', 'CLOSED')) DEFAULT 'OPEN'
);

-- 8. Tabel Transactions
CREATE TABLE transactions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Menggunakan UUID agar aman di-generate offline
    store_id UUID REFERENCES stores(id),
    user_id UUID REFERENCES users(id),
    shift_id UUID REFERENCES shifts(id),
    member_id UUID REFERENCES members(id),
    invoice_number VARCHAR(100) UNIQUE NOT NULL,
    subtotal DECIMAL(15,2) NOT NULL,
    discount_amount DECIMAL(15,2) DEFAULT 0.00,
    tax_amount DECIMAL(15,2) DEFAULT 0.00,
    rounding_amount DECIMAL(15,2) DEFAULT 0.00,
    grand_total DECIMAL(15,2) NOT NULL,
    payment_method VARCHAR(30) CHECK (payment_method IN ('CASH', 'QRIS', 'DEBIT_CARD', 'CREDIT_CARD', 'E_WALLET', 'SPLIT')),
    payment_status VARCHAR(20) CHECK (payment_status IN ('PAID', 'REFUNDED')) DEFAULT 'PAID',
    is_synced BOOLEAN DEFAULT FALSE, -- Flag sinkronisasi offline ke online
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 9. Tabel Transaction Details
CREATE TABLE transaction_details (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    transaction_id UUID REFERENCES transactions(id) ON DELETE CASCADE,
    product_variant_id UUID REFERENCES product_variants(id),
    qty INT NOT NULL,
    unit_price DECIMAL(15,2) NOT NULL,
    discount_amount DECIMAL(15,2) DEFAULT 0.00,
    tax_amount DECIMAL(15,2) DEFAULT 0.00,
    net_price DECIMAL(15,2) NOT NULL, -- (unit_price * qty) - discount_amount
    commission_earned DECIMAL(15,2) DEFAULT 0.00 -- Komisi kasir dari item ini
);

4.3 Strategi Pengindeksan & Partisi Data

  • Compound Index untuk POS Sync: CREATE INDEX idx_transactions_sync ON transactions (store_id, is_synced, created_at DESC);
  • Partisi Tabel Transaksi: Guna menjaga performa pembacaan laporan pada database cloud utama, tabel transactions di-partisi berdasarkan rentang waktu bulanan menggunakan deklarasi PostgreSQL: PARTITION BY RANGE (created_at);
  • Index Pencarian Barcode: CREATE UNIQUE INDEX idx_variant_barcode ON product_variants(variant_barcode) WHERE variant_barcode IS NOT NULL;