Lewati ke isi

Sentra Automotive — Database Design

Entity-Relationship Diagram (ERD)

Diagram relasi tabel inti sistem manajemen bengkel dan gudang sparepart:

erDiagram
    CUSTOMERS {
        uuid id PK
        varchar full_name
        varchar phone_number UK
        varchar email
    }
    VEHICLES {
        uuid id PK
        uuid customer_id FK
        varchar license_plate UK
        varchar vin_number UK
        varchar engine_number
        varchar brand_model
        integer production_year
    }
    WORK_ORDERS {
        uuid id PK
        uuid vehicle_id FK
        uuid branch_id FK
        uuid service_advisor_id FK
        varchar wo_number UK
        integer current_odometer
        text customer_complaints
        varchar status
        timestamp created_at
    }
    JOB_ITEMS {
        uuid id PK
        uuid work_order_id FK
        uuid mechanic_id FK
        varchar item_type
        varchar name
        numeric estimated_price
        numeric final_price
        varchar status
    }
    SPAREPARTS {
        uuid id PK
        varchar sku_number UK
        varchar name
        varchar brand
        varchar category
        varchar unit
        varchar barcode_data UK
    }
    SPAREPART_BATCHES {
        uuid id PK
        uuid sparepart_id FK
        varchar batch_number
        integer current_stock
        numeric purchase_price
        numeric selling_price
    }
    SPAREPART_SUBSTITUTIONS {
        uuid id PK
        uuid parent_sparepart_id FK
        uuid substitute_sparepart_id FK
        varchar compatibility_level
    }
    INVOICES {
        uuid id PK
        uuid work_order_id FK
        varchar invoice_number UK
        numeric parts_subtotal
        numeric services_subtotal
        numeric tax_amount
        numeric grand_total
        varchar payment_status
    }
    MECHANIC_COMMISSIONS {
        uuid id PK
        uuid mechanic_id FK
        uuid job_item_id FK
        numeric commission_amount
        timestamp calculated_at
    }

    CUSTOMERS ||--o{ VEHICLES : owns
    VEHICLES ||--o{ WORK_ORDERS : has
    WORK_ORDERS ||--o{ JOB_ITEMS : contains
    WORK_ORDERS ||--|| INVOICES : bills
    JOB_ITEMS ||--o{ MECHANIC_COMMISSIONS : generates
    SPAREPARTS ||--o{ SPAREPART_BATCHES : tracks
    SPAREPARTS ||--o{ SPAREPART_SUBSTITUTIONS : maps
    SPAREPARTS ||--o{ JOB_ITEMS : links

Production PostgreSQL DDL Schema

Skema DDL SQL lengkap dengan constraint ketat, indeks plat nomor, penanganan substitusi part, dan audit trails.

-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- 1. Table Customers & Vehicles
CREATE TABLE customers (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    full_name VARCHAR(100) NOT NULL,
    phone_number VARCHAR(20) UNIQUE NOT NULL,
    email VARCHAR(100),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE vehicles (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE RESTRICT,
    license_plate VARCHAR(20) UNIQUE NOT NULL, -- e.g., 'B 1234 ABC'
    vin_number VARCHAR(30) UNIQUE NOT NULL,    -- Nomor Rangka
    engine_number VARCHAR(30) UNIQUE NOT NULL, -- Nomor Mesin
    brand_model VARCHAR(100) NOT NULL,         -- e.g., 'Honda Civic 1.5 Turbo'
    production_year INTEGER NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_vehicles_plate ON vehicles(license_plate);
CREATE INDEX idx_vehicles_vin ON vehicles(vin_number);

-- 2. Table Work Orders (WO)
CREATE TABLE work_orders (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE RESTRICT,
    branch_id UUID NOT NULL,
    service_advisor_id UUID NOT NULL,
    wo_number VARCHAR(50) UNIQUE NOT NULL, -- e.g., 'WO/2026/05/0091'
    current_odometer INTEGER NOT NULL,
    customer_complaints TEXT NOT NULL,
    inspection_checklist JSONB DEFAULT '{}'::jsonb NOT NULL,
    status VARCHAR(20) CHECK (status IN (
        'INTAKE', 'DIAGNOSING', 'WORKING', 'WAITING_PARTS',
        'QC_TEST', 'COMPLETED', 'CANCELLED', 'CLAIM_REWORK'
    )) DEFAULT 'INTAKE' NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_wo_number ON work_orders(wo_number);
CREATE INDEX idx_wo_status ON work_orders(status);

-- 3. Table Spareparts Master, Batches, & Substitutions
CREATE TABLE spareparts (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    sku_number VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    brand VARCHAR(50) NOT NULL,
    category VARCHAR(50) CHECK (category IN ('FAST_MOVING', 'SLOW_MOVING', 'ACCESSORIES')) NOT NULL,
    unit VARCHAR(20) NOT NULL, -- e.g., 'Pcs', 'Liter', 'Set'
    barcode_data VARCHAR(100) UNIQUE NOT NULL,
    min_stock_alert INTEGER DEFAULT 10 NOT NULL
);

CREATE TABLE sparepart_batches (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    sparepart_id UUID NOT NULL REFERENCES spareparts(id) ON DELETE RESTRICT,
    batch_number VARCHAR(50) NOT NULL,
    current_stock INTEGER NOT NULL CHECK (current_stock >= 0),
    purchase_price NUMERIC(12,2) NOT NULL,
    selling_price NUMERIC(12,2) NOT NULL,
    UNIQUE (sparepart_id, batch_number)
);

-- Substitute mapping: EXACT (drop-in replacement) or MODIFIED (compatible with adjustment)
CREATE TABLE sparepart_substitutions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    parent_sparepart_id UUID NOT NULL REFERENCES spareparts(id) ON DELETE CASCADE,
    substitute_sparepart_id UUID NOT NULL REFERENCES spareparts(id) ON DELETE CASCADE,
    compatibility_level VARCHAR(20) CHECK (compatibility_level IN ('EXACT', 'MODIFIED')) NOT NULL,
    UNIQUE (parent_sparepart_id, substitute_sparepart_id)
);

-- 4. Table Work Order Job Items (Labor & Parts mapping in SPK)
CREATE TABLE job_items (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    work_order_id UUID NOT NULL REFERENCES work_orders(id) ON DELETE CASCADE,
    mechanic_id UUID REFERENCES users(id) ON DELETE RESTRICT,
    item_type VARCHAR(20) CHECK (item_type IN ('SERVICE_LABOR', 'SPAREPART')) NOT NULL,
    sparepart_id UUID REFERENCES spareparts(id) ON DELETE RESTRICT, -- Null if SERVICE_LABOR
    name VARCHAR(150) NOT NULL,
    quantity INTEGER DEFAULT 1 NOT NULL CHECK (quantity > 0),
    estimated_price NUMERIC(12,2) NOT NULL,
    final_price NUMERIC(12,2) NOT NULL,
    status VARCHAR(30) CHECK (status IN (
        'PENDING', 'APPROVED_BY_CUSTOMER', 'REJECTED_BY_CUSTOMER', 'COMPLETED'
    )) DEFAULT 'PENDING' NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 5. Table Invoices
CREATE TABLE invoices (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    work_order_id UUID UNIQUE NOT NULL REFERENCES work_orders(id) ON DELETE RESTRICT,
    invoice_number VARCHAR(50) UNIQUE NOT NULL, -- e.g., 'INV/2026/05/0091'
    parts_subtotal NUMERIC(12,2) DEFAULT 0.00 NOT NULL,
    services_subtotal NUMERIC(12,2) DEFAULT 0.00 NOT NULL,
    tax_amount NUMERIC(12,2) DEFAULT 0.00 NOT NULL,  -- PPN 11%
    grand_total NUMERIC(12,2) DEFAULT 0.00 NOT NULL,
    down_payment NUMERIC(12,2) DEFAULT 0.00 NOT NULL,
    payment_status VARCHAR(20) CHECK (payment_status IN (
        'UNPAID', 'PAID', 'REFUNDED', 'ABANDONED_ALERT'
    )) DEFAULT 'UNPAID' NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 6. Table Mechanic Commissions & Payroll
CREATE TABLE mechanic_commissions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    mechanic_id UUID NOT NULL,
    job_item_id UUID UNIQUE NOT NULL REFERENCES job_items(id) ON DELETE RESTRICT,
    commission_type VARCHAR(20) CHECK (commission_type IN (
        'FLAT_RATE', 'PERCENTAGE', 'UPSELL_BONUS'
    )) NOT NULL,
    commission_amount NUMERIC(12,2) NOT NULL,
    calculated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 7. Auto-reorder purchase requisitions
CREATE TABLE purchase_requisitions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    sparepart_id UUID NOT NULL REFERENCES spareparts(id) ON DELETE RESTRICT,
    branch_id UUID NOT NULL,
    quantity_requested INTEGER NOT NULL CHECK (quantity_requested > 0),
    status VARCHAR(30) CHECK (status IN (
        'PENDING_SUPPLIER_APPROVAL', 'APPROVED', 'FULFILLED', 'CANCELLED'
    )) DEFAULT 'PENDING_SUPPLIER_APPROVAL' NOT NULL,
    reason TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

Key Design Decisions

Keputusan Alasan
sparepart_batches terpisah dari spareparts Mendukung FEFO (First Expired First Out) dispense — stok terdistribusi per batch dengan harga beli/jual berbeda
sparepart_substitutions dengan compatibility_level Dua level substitusi: EXACT (langsung tukar) dan MODIFIED (butuh penyesuaian mekanis)
inspection_checklist JSONB di work_orders Skema checklist fleksibel per jenis kendaraan tanpa DDL migration tiap ada field baru
min_stock_alert di spareparts Threshold untuk auto-reorder engine — sistem auto-insert purchase_requisitions saat current_stock <= min_stock_alert
CLAIM_REWORK status WO Status khusus untuk garansi rework — memungkinkan mekanik asal dikenai quality penalty deduction otomatis