Bengkel Pintar ERP — Flow Diagram & PRD Documentation
19 Modul 100+ Endpoint Multi-Cabang

🔧 Bengkel Pintar ERP

Dokumentasi lengkap alur proses, diagram flow, dan approval workflow untuk seluruh modul sistem ERP manajemen bengkel otomotif multi-cabang.

19
Modul Aktif
100+
API Endpoint
7
Role Pengguna
118
Halaman UI
Multi-Cabang
🏗️

Arsitektur Sistem

Gambaran besar sistem Bengkel Pintar ERP — stack teknologi, layar aplikasi, dan hubungan antar komponen utama.

Bun + Hono Next.js 16 PostgreSQL Prisma ORM MinIO

🖥️ Tiga Lapisan Aplikasi

  • 1
    bengkel-landing — Halaman marketing SaaS (Next.js, port 3001). Berisi fitur, harga, halaman demo, dan form permintaan demo yang terhubung ke backend via POST /api/marketing/demo-request.
  • 2
    bengkel-admin — Aplikasi operasional utama (Next.js 16 + React 19, port 3000). 118 halaman, 5 grup role. Semua request ke backend via Authorization: Bearer {token}.
  • 3
    bengkel-pintar-api — Backend Hono + Bun (port 4000). 19 modul, 100+ endpoint. Autentikasi JWT, RBAC, multi-cabang, double-entry accounting.

⚙️ Stack Teknologi

LayerTeknologi
Runtime APIBun + Hono (TypeScript)
FrontendNext.js 16, React 19, Tailwind v4
DatabasePostgreSQL + Prisma ORM
File StorageMinIO (S3-compatible, self-hosted)
AuthJWT dual-token (access 15m + refresh 7d)
PDFGenerate SPK, Invoice A4, Struk Termal
NotifikasiWhatsApp (channel notifikasi pelanggan)

🏢 Konsep Multi-Cabang

  • Setiap data transaksi (WO, stok, karyawan) terikat ke branchId.
  • Role admin dapat melihat dan mengelola semua cabang.
  • Role manager hanya dapat mengakses data cabang sendiri (branch-scoped).
  • Harga jasa dapat di-override per cabang via BranchJasaPrice.
  • Stok dikelola per cabang via BranchStock, transfer antar cabang memerlukan approval.
  • Laporan cabang dapat dibandingkan via endpoint GET /api/dashboard (branchComparison).

🌐 Arsitektur Aplikasi & Integrasi Komponen

graph TB subgraph CLIENT["🖥️ Client Layer"] LANDING["bengkel-landing
Next.js · Port 3001
SaaS Marketing Site"] ADMIN["bengkel-admin
Next.js 16 + React 19
Port 3000"] end subgraph API["⚙️ Backend API — bengkel-pintar-api"] direction TB HONO["Hono Framework
Bun Runtime · Port 4000"] AUTH_MW["Auth Middleware
JWT Verify + RBAC"] MODULES["19 Route Modules
workorder · quotation · kasir
inventory · hcm · finance · operator"] HONO --> AUTH_MW --> MODULES end subgraph DATA["🗄️ Data Layer"] PRISMA["Prisma ORM"] PG["PostgreSQL
Database"] MINIO["MinIO
Object Storage
Port 9000/9001"] PRISMA --> PG end subgraph INTEGRATIONS["🔗 Integrasi"] PDF["PDF Generator
SPK · Invoice · Receipt"] WA["WhatsApp
Notifikasi"] REPORT["Laporan
Balance Sheet · P&L"] end LANDING -->|"Demo Request API"| HONO ADMIN -->|"REST API + JWT"| HONO MODULES --> PRISMA MODULES --> MINIO MODULES --> PDF MODULES --> WA MODULES --> REPORT style CLIENT fill:#dbeafe,stroke:#3b82f6 style API fill:#ede9fe,stroke:#8b5cf6 style DATA fill:#d1fae5,stroke:#10b981 style INTEGRATIONS fill:#fef3c7,stroke:#f59e0b

👥 Role Pengguna & Akses Modul

graph LR subgraph ROLES["Role Sistem"] ADM["🛡️ Admin
Full Access"] MGR["📊 Manager
Branch Scoped"] SA["🎧 Service Advisor
WO + Quotation"] MKN["🔧 Mekanik
Job Execution"] KSR["💰 Kasir
Payment + Invoice"] GDG["📦 Gudang
Inventory + PO"] OPR["📋 Operator
Booking + Checkin"] end subgraph MODULES["Akses Modul"] M1["Work Order"] M2["Quotation"] M3["Kasir"] M4["Inventory"] M5["HCM"] M6["Finance"] M7["Booking"] M8["Master Data"] end ADM --> M1 & M2 & M3 & M4 & M5 & M6 & M7 & M8 MGR --> M1 & M2 & M4 & M5 & M6 & M7 SA --> M1 & M2 MKN --> M1 KSR --> M3 GDG --> M4 OPR --> M7 style ADM fill:#fee2e2,stroke:#ef4444 style MGR fill:#dbeafe,stroke:#3b82f6 style SA fill:#d1fae5,stroke:#10b981 style MKN fill:#fef3c7,stroke:#f59e0b style KSR fill:#ede9fe,stroke:#8b5cf6 style GDG fill:#ccfbf1,stroke:#14b8a6 style OPR fill:#ffedd5,stroke:#f97316
🔐

Autentikasi & Manajemen User

Sistem login dengan JWT dual-token (access + refresh), reset password, dan manajemen user berbasis RBAC per cabang.

JWT Auth RBAC Rate Limiting Multi-Cabang

🔑 Mekanisme JWT Dual-Token

  • 1
    User login → server mengembalikan accessToken (berlaku 15 menit) dan refreshToken (berlaku 7 hari).
  • 2
    Setiap request API menyertakan Authorization: Bearer {accessToken} di header.
  • 3
    Jika accessToken expired (401), frontend otomatis hit POST /auth/refresh menggunakan refreshToken untuk mendapat accessToken baru.
  • 4
    Jika refreshToken juga expired → user di-logout otomatis dan diredirect ke halaman login.
  • !
    Login dibatasi 10 percobaan per 60 detik per IP (rate limiting) untuk mencegah brute force.

🔄 Redirect Berdasarkan Role (ROLE_REDIRECT)

RoleRedirect Setelah Login
admin/dashboard
manager/dashboard
sa/sa (Service Advisor)
mekanik/mekanik
kasir/kasir
gudang/dashboard
operator/operator

🛡️ Manajemen User oleh Admin

  • Admin dapat membuat user baru dan menetapkan role serta cabang.
  • Admin dapat toggle status user (aktif/nonaktif) tanpa menghapus data.
  • Admin dapat mengatur allowedPages[] untuk memberikan akses ke halaman tertentu di luar role default.
  • Admin dapat mengatur deniedPages[] untuk memblokir halaman tertentu meski role memiliki izin.
  • Reset password dapat dilakukan admin tanpa mengetahui password lama user.
  • Semua RefreshToken direvoke otomatis saat password direset.

🔑 Alur Login & Token Management

sequenceDiagram actor U as User / Browser participant FE as Frontend (Admin) participant API as Backend API participant DB as Database U->>FE: Input email + password FE->>API: POST /api/auth/login Note over API: Rate limit: 10x/60s API->>DB: Cek user aktif + password hash alt Kredensial valid DB-->>API: User data + role + branch API-->>FE: accessToken (15m) + refreshToken (7d) FE->>FE: Simpan token, redirect role Note over FE: ROLE_REDIRECT map
admin→/dashboard
sa→/sa
mekanik→/mekanik else Gagal API-->>FE: 401 Unauthorized FE-->>U: Tampilkan error end Note over FE,API: Setiap request API FE->>API: Header: Authorization Bearer accessToken API->>API: Verify JWT + check permissions alt Token expired (401) FE->>API: POST /api/auth/refresh API-->>FE: accessToken baru FE->>API: Retry original request else Refresh expired FE->>FE: Logout + redirect /login end

🔄 Alur Reset Password

flowchart TD A([User lupa password]) --> B[POST /auth/forgot-password\nemail] B --> C{Email terdaftar?} C -->|Tidak| D[Response 200 tetap\nuntuk keamanan] C -->|Ya| E[Buat PasswordResetToken\nTTL 1 jam] E --> F[Kirim email\nlink reset] F --> G([User klik link]) G --> H[POST /auth/reset-password\ntoken + newPassword] H --> I{Token valid\n& belum dipakai?} I -->|Tidak / Expired| J[Error: Token invalid] I -->|Ya| K[Update password hash\nMark token used] K --> L[Revoke semua RefreshToken] L --> M([Redirect ke login]) style A fill:#fee2e2,stroke:#ef4444 style M fill:#d1fae5,stroke:#10b981 style D fill:#f1f5f9,stroke:#94a3b8 style J fill:#fee2e2,stroke:#ef4444

🛡️ Manajemen User & Permission (Admin)

flowchart TD ADM([Admin]) --> A{Aksi User} A -->|Buat user| B[POST /api/users\nnama·email·role·cabang] B --> C[Generate temp password] C --> D[Kirim notif ke user] A -->|Edit user| E[PUT /api/users/:id\nupdate profil] A -->|Toggle aktif| F[PATCH /api/users/:id/toggle\naktif / nonaktif] A -->|Reset password| G[PUT /api/users/:id/reset-password] A -->|Set permissions| H[PUT /api/users/:id/permissions\nallowedPages + deniedPages] H --> I{Mode Permission} I -->|allowedPages| J[Izinkan halaman\ndi luar role] I -->|deniedPages| K[Blokir halaman\nmeski role punya akses] style ADM fill:#fee2e2,stroke:#ef4444 style J fill:#d1fae5,stroke:#10b981 style K fill:#fee2e2,stroke:#ef4444

📋 Struktur Data User & Token

erDiagram User { string id PK string email UK string name string role string branchId FK boolean isActive string[] allowedPages string[] deniedPages string roleId FK } RefreshToken { string id PK string userId FK string token UK datetime expiresAt } PasswordResetToken { string id PK string userId FK string token UK boolean used datetime expiresAt } Role { string id PK string branchId FK string name boolean isSystem } RolePermission { string roleId FK string resource boolean canView boolean canCreate boolean canEdit boolean canDelete } User ||--o{ RefreshToken : "memiliki" User ||--o{ PasswordResetToken : "memiliki" User }o--|| Role : "memiliki custom role" Role ||--o{ RolePermission : "mendefinisikan"
📋

Work Order (SPK — Surat Perintah Kerja)

Inti dari sistem operasional bengkel. Mengelola seluruh siklus servis kendaraan dari antrian masuk hingga kendaraan diserahkan ke pelanggan, termasuk penugasan mekanik, tracking pekerjaan per-item, dan pembayaran.

Core Module 7 Status Job Tracking PDF Export

📋 6 Status Work Order

StatusArtinyaAktor
antrianKendaraan masuk, menunggu giliran diagnosaSA / Sistem
diagnosaSA dan mekanik memeriksa kendaraan, estimasi dibuatSA
pengerjaanMekanik aktif mengerjakan job per itemMekanik
quality_checkPemeriksaan akhir hasil kerja sebelum diserahkanKepala Mekanik / SA
selesaiKendaraan siap, menunggu pembayaran lunasSistem otomatis
diserahkanKendaraan sudah diserahkan ke pelangganSA

💳 4 Status Pembayaran WO

StatusKondisi
belumBelum ada pembayaran sama sekali
dpSudah bayar sebagian (down payment)
lunasPembayaran penuh, WO bisa diserahkan
tempoKredit — bayar belakangan sesuai kesepakatan

WO hanya bisa berstatus diserahkan jika paymentStatus = lunas dan WO status = selesai.

⚙️ Job Tracking per Item

  • Setiap WOItem (jasa atau parts) dapat memiliki satu WOJob yang di-assign ke mekanik tertentu.
  • Mekanik dapat start → pause → resume → complete job dengan timer otomatis.
  • Sistem mencatat estMinutes (estimasi) vs actualMinutes (aktual) untuk KPI.
  • WO baru dapat masuk Quality Check jika semua WOJob sudah berstatus done.
  • Mekanik dapat di-pause untuk request parts ke gudang, lalu resume setelah parts diterima.

👥 Aktor Terlibat

🎧 Service Advisor 🔧 Mekanik 💰 Kasir 📦 Gudang 📊 Manager
  • SA — membuat SPK, assign mekanik, ubah status, serahkan kendaraan.
  • Mekanik — mengambil job, tracking waktu, request parts.
  • Kasir — menerima pembayaran, cetak invoice dan struk.
  • Gudang — menyediakan parts yang di-request mekanik.

🔄 Lifecycle Work Order — Status Flow Lengkap

flowchart TD START(["SPK Baru\nWalk-in / Booking / Quotation"]) --> antrian antrian["🟡 ANTRIAN\nKendaraan terdaftar\nmenunggu antrian"] diagnosa["🔵 DIAGNOSA\nSA dan Mekanik lakukan\npemeriksaan & estimasi"] pengerjaan["🟠 PENGERJAAN\nMekanik kerjakan tiap WO Item\nJob tracking: start / pause / done"] quality_check["🟣 QUALITY CHECK\nPemeriksaan akhir hasil kerja\noleh kepala mekanik atau SA"] selesai["🟢 SELESAI\nKendaraan siap diserahkan\nMenunggu pembayaran lunas"] diserahkan["✅ DISERAHKAN\nKendaraan diterima pelanggan\nProses servis selesai"] antrian -->|"SA mulai diagnosa"| diagnosa diagnosa -->|"Konfirmasi pelanggan + assign mekanik"| pengerjaan pengerjaan -->|"Semua job selesai"| quality_check quality_check -->|"Ada perbaikan ulang"| pengerjaan quality_check -->|"QC passed"| selesai selesai -->|"Pembayaran LUNAS + serah terima"| diserahkan diserahkan --> DONE(["Selesai"]) style START fill:#f1f5f9,stroke:#94a3b8 style antrian fill:#fef3c7,stroke:#f59e0b,color:#92400e style diagnosa fill:#dbeafe,stroke:#3b82f6,color:#1e40af style pengerjaan fill:#ffedd5,stroke:#f97316,color:#9a3412 style quality_check fill:#ede9fe,stroke:#8b5cf6,color:#5b21b6 style selesai fill:#d1fae5,stroke:#10b981,color:#065f46 style diserahkan fill:#d1fae5,stroke:#10b981,color:#065f46 style DONE fill:#d1fae5,stroke:#10b981

📝 Proses Pembuatan Work Order (SA Flow)

flowchart TD START([SA / Admin mulai]) --> SRC{Sumber SPK} SRC -->|Walk-in| A[Cari / buat Customer] SRC -->|Dari Booking| B[Ambil data Booking] SRC -->|Dari Quotation| C[POST quotation/:id/convert] A & B & C --> D[Pilih / daftarkan Kendaraan] D --> E[Isi data SPK:\n• Odometer\n• Keluhan pelanggan\n• Tipe servis\n• Level bahan bakar\n• Estimasi selesai\n• Bay / Lift] E --> F[Tambah WO Items] F --> G{Tipe item?} G -->|Jasa| H[Pilih dari master Jasa\natau input manual] G -->|Parts| I[Pilih dari master Part\nstok otomatis dicek] H & I --> J[Set qty, harga,\ndiskon, estimasi menit] J --> K{Tambah item lagi?} K -->|Ya| F K -->|Tidak| L[POST /api/workorders\nStatus: ANTRIAN] L --> M[Assign Mekanik\nPOST /:id/mechanics] M --> N[Cetak SPK PDF\nGET /api/spk/:id/pdf] N --> O([SPK aktif, proses mulai]) style START fill:#dbeafe,stroke:#3b82f6 style O fill:#d1fae5,stroke:#10b981

⚙️ Job Tracking — Alur Kerja Mekanik per Item

flowchart TD START([Job dibuat saat WO Item ditambahkan]) --> waiting waiting[WAITING\nMenunggu mekanik\nmulai pengerjaan] running[RUNNING\nMekanik aktif mengerjakan\nTimer berjalan otomatis] paused[PAUSED\nDijeda sementara\nmisal tunggu parts] done[DONE\nPekerjaan selesai\nwaktu aktual tercatat] check{Semua job WO selesai?} waiting -->|start jobs/:id/start| running running -->|pause jobs/:id/pause| paused paused -->|lanjut jobs/:id/start| running running -->|selesai jobs/:id/complete| done done --> check check -->|Ya semua selesai| QC([WO masuk QUALITY CHECK]) check -->|Masih ada job lain| waiting style START fill:#f1f5f9,stroke:#94a3b8 style waiting fill:#fef3c7,stroke:#f59e0b style running fill:#dbeafe,stroke:#3b82f6 style paused fill:#ede9fe,stroke:#8b5cf6 style done fill:#d1fae5,stroke:#10b981 style QC fill:#d1fae5,stroke:#10b981

💳 Status Pembayaran Work Order

flowchart LR A[belum\n❌ Belum bayar] -->|Record DP| B[dp\n💛 Down Payment\nbayar sebagian] B -->|Lunasi sisa| C[lunas\n✅ Lunas] A -->|Bayar penuh| C A -->|Kredit disetujui| D[tempo\n📅 Kredit / Cicilan] D -->|Lunasi cicilan| C C --> E{WO Status = selesai?} E -->|Ya| F[Bisa DISERAHKAN] E -->|Tidak| G[Tunggu pengerjaan selesai] style A fill:#fee2e2,stroke:#ef4444 style B fill:#fef3c7,stroke:#f59e0b style C fill:#d1fae5,stroke:#10b981 style D fill:#ede9fe,stroke:#8b5cf6 style F fill:#d1fae5,stroke:#10b981

🗂️ Struktur Data Work Order (ERD)

erDiagram WorkOrder { string displayId string status string paymentStatus int odometer string complaint decimal subtotal decimal ppn decimal total } WOItem { string type string name int qty decimal price decimal disc int estMin } WOJob { string status int estMinutes int actualMinutes datetime startedAt datetime completedAt } WODamage { string zone string type string notes } MechanicAssignment { string workOrderId string employeeId datetime assignedAt } Payment { decimal amount string method string type } WorkOrder ||--o{ WOItem : "berisi" WorkOrder ||--o{ WOJob : "punya job" WorkOrder ||--o{ WODamage : "dokumen damage" WorkOrder ||--o{ MechanicAssignment : "assign mekanik" WorkOrder ||--o{ Payment : "dicatat pembayaran" WOItem ||--o| WOJob : "tracking job"
💬

Quotation / Estimasi Harga

Modul untuk membuat penawaran harga servis sebelum pekerjaan dimulai. Quotation yang disetujui pelanggan dapat langsung dikonversi menjadi Work Order.

Pre-WO Approval Convert to SPK

📊 5 Status Quotation

StatusArtinya
pendingEstimasi dibuat, menunggu konfirmasi pelanggan
approvedPelanggan setuju, siap dikonversi ke SPK
rejectedPelanggan menolak, penawaran tidak dilanjutkan
expiredMelewati tanggal validitas (validUntil)
convertedSudah dikonversi menjadi Work Order aktif

🔄 Proses Konversi ke SPK

  • 1
    SA buat Quotation, tambahkan item jasa dan parts beserta harga.
  • 2
    Quotation dikirim ke pelanggan via email, cetak, atau WhatsApp.
  • 3
    Pelanggan bisa nego — SA update item/harga, lalu kirim ulang.
  • 4
    Pelanggan setuju → status jadi approved.
  • 5
    SA klik Convert → sistem buat WO baru, salin semua item, status WO: antrian.
  • !
    Quotation yang sudah converted tidak bisa diubah lagi.

📋 Aturan Bisnis

  • Quotation hanya bisa diedit selama berstatus pending.
  • Tanggal validUntil diset saat pembuatan, sistem menandai expired otomatis setelah tanggal ini.
  • Harga di Quotation tidak otomatis mengikuti perubahan harga master jasa — harga dikunci saat item ditambahkan.
  • Setelah konversi, Quotation berstatus converted dan Work Order baru mendapat nomor SPK sendiri (displayId).
  • Quotation yang rejected atau expired tidak dapat diaktifkan kembali, harus buat baru.

📊 Flow Status Quotation

flowchart LR START([Quotation Baru\nSA buat estimasi]) --> pending pending[PENDING\nEstimasi dibuat\nmenunggu konfirmasi pelanggan] approved[APPROVED\nPelanggan setuju\nsiap dikonversi ke SPK] rejected[REJECTED\nPelanggan menolak\npenawaran] expired[EXPIRED\nMelewati tanggal\nvaliditas] converted[CONVERTED\nSudah dikonversi\nmenjadi Work Order] pending -->|Pelanggan konfirmasi| approved pending -->|Pelanggan tolak| rejected pending -->|validUntil terlewati| expired approved -->|POST convert - buat SPK| converted approved -->|Batal setelah disetujui| rejected rejected --> DONE1([Selesai]) expired --> DONE2([Selesai]) converted --> DONE3([Selesai]) style START fill:#f1f5f9,stroke:#94a3b8 style pending fill:#fef3c7,stroke:#f59e0b style approved fill:#d1fae5,stroke:#10b981 style rejected fill:#fee2e2,stroke:#ef4444 style expired fill:#f1f5f9,stroke:#94a3b8 style converted fill:#dbeafe,stroke:#3b82f6

🔄 Proses Quotation ke Work Order

flowchart TD A([SA buat Quotation]) --> B[POST /api/quotations\nisi jasa + parts + harga] B --> C[Kirim ke pelanggan\nemail / cetak / WA] C --> D{Respons pelanggan} D -->|Setuju| E[PATCH status → approved] D -->|Nego| F[PUT update items/harga] F --> C D -->|Tolak| G[PATCH status → rejected] D -->|Tidak respons| H{Cek tanggal validitas} H -->|Expired| I[PATCH status → expired] E --> J[POST /:id/convert] J --> K[Buat Work Order baru\nSalin semua item\nStatus: ANTRIAN] K --> L[Quotation status → converted] L --> M([Lanjut ke alur SPK]) style A fill:#dbeafe,stroke:#3b82f6 style M fill:#d1fae5,stroke:#10b981 style G fill:#fee2e2,stroke:#ef4444 style I fill:#fef3c7,stroke:#f59e0b
📞

Operator — Booking, Check-in & Follow-up

Modul front-desk untuk mengelola reservasi servis, penerimaan kendaraan, dan tindak lanjut pelanggan (servis berkala, pengingat STNK/asuransi, pelanggan tidak aktif).

Customer Facing Booking Follow-up CRM

📅 5 Status Booking

StatusArtinya
scheduledBooking dibuat, belum dikonfirmasi ke pelanggan
confirmedOperator sudah konfirmasi jadwal ke pelanggan
arrivedPelanggan sudah tiba, proses check-in dilakukan
no_showPelanggan tidak datang pada waktu yang dijadwalkan
cancelledBooking dibatalkan oleh pelanggan atau operator

📬 6 Tipe Follow-up Otomatis

TipeTrigger
servis_berkalaKm atau bulan interval servis sudah terlewati
stnk_expiredTanggal STNK kendaraan mendekati kadaluarsa
insurance_expiredAsuransi kendaraan mendekati kadaluarsa
inactive_customerPelanggan tidak servis lebih dari 3 bulan
post_serviceSurvey kepuasan setelah servis selesai
manualDibuat manual oleh operator

🔄 Alur Status Follow-up

  • 1
    PENDING — Follow-up masuk antrean operator.
  • 2
    CONTACTED — Operator sudah menghubungi pelanggan via WA / Telepon.
  • 3a
    SCHEDULED — Pelanggan mau booking servis, tanggal berikutnya dijadwalkan.
  • 3b
    COMPLETED — Pelanggan tidak perlu servis sekarang, follow-up selesai.
  • 3c
    SKIPPED — Tidak relevan atau tidak bisa dihubungi, di-skip.

📅 Alur Booking & Penerimaan Kendaraan

flowchart TD START([Pelanggan]) --> CH{Channel booking} CH -->|Telepon / WA| OPR[Operator input di sistem] CH -->|Langsung datang| CI[Walk-in Check-in] CH -->|Online| BOOK[Self-booking future] OPR --> A[POST /api/operator/bookings\nisi: tanggal, jam, layanan, keluhan] A --> B[Status: SCHEDULED\nTerjadwal] B --> C[Operator konfirmasi ke pelanggan] C --> D[PATCH status: CONFIRMED\nDikonfirmasi] D --> E{Hari H} E -->|Pelanggan datang| F[PATCH status: ARRIVED\nTiba] E -->|Tidak datang| G[PATCH status: NO_SHOW\nTidak hadir] E -->|Batal| H[PATCH status: CANCELLED\nDibatalkan] F --> I[Check-in kendaraan\nPOST /api/operator/checkin] CI --> I I --> J[Cek data customer + kendaraan\nKeluhan + odometer] J --> K[SA buat Work Order\nPOST /api/workorders] K --> L([SPK aktif → alur servis]) style START fill:#dbeafe,stroke:#3b82f6 style L fill:#d1fae5,stroke:#10b981 style G fill:#fee2e2,stroke:#ef4444 style H fill:#fee2e2,stroke:#ef4444

📬 Alur Follow-up Pelanggan

flowchart TD TRIGGER{Trigger Follow-up} -->|Sistem otomatis| AUTO TRIGGER -->|Operator manual| MANUAL[POST /api/operator/followup\ntipe: manual] subgraph AUTO["Trigger Otomatis"] A1["servis_berkala\nKm / bulan interval terlewati"] A2["stnk_expired\nSurat kendaraan mau habis"] A3["insurance_expired\nAsuransi mau habis"] A4["inactive_customer\n> 3 bulan tidak servis"] A5["post_service\nSurvey kepuasan"] end AUTO --> B[Status: PENDING\nMenunggu ditindaklanjuti] MANUAL --> B B --> C[Operator hubungi pelanggan\nvia WA / Telepon] C --> D[PATCH status: CONTACTED\nSudah dihubungi] D --> E{Hasil kontak} E -->|Mau booking servis| F[PATCH status: SCHEDULED\nDijadwalkan] E -->|Tidak perlu sekarang| G[PATCH status: COMPLETED\nSelesai] E -->|Skip atau tidak relevan| H[PATCH status: SKIPPED\nDilewati] F --> I[Set nextFollowup date\nuntuk reminder berikutnya] G & H --> J([Rekam ke audit log]) style TRIGGER fill:#dbeafe,stroke:#3b82f6 style J fill:#d1fae5,stroke:#10b981
💰

Kasir & Pembayaran

Modul pengelolaan pembayaran, rekonsiliasi kas, dan pembuatan dokumen keuangan (invoice A4, struk termal). Mendukung multi-metode bayar dan sistem laci kas.

Payment Processing Cash Drawer PDF Invoice Multi-Metode

💳 5 Metode Pembayaran

MetodeKeterangan
cashTunai — langsung dicatat ke laci kas aktif
transferTransfer bank — kasir input nomor referensi
debit_cardKartu debit via mesin EDC
credit_cardKartu kredit via mesin EDC
qrisScan QR — terintegrasi via payment gateway

🗄️ Laci Kas (Cash Drawer)

  • 1
    Awal shift: kasir buka laci kas dan input saldo awal (opening balance).
  • 2
    Setiap transaksi tunai otomatis tercatat: kas_masuk, kas_keluar, atau pembayaran_spk.
  • 3
    Akhir shift: kasir tutup laci kas, input jumlah uang fisik yang dihitung.
  • 4
    Sistem hitung selisih (difference) antara kas tercatat vs fisik untuk rekonsiliasi.
  • !Laci yang sudah closed tidak bisa dibuka ulang — harus buat laci baru.

📄 Dokumen yang Dicetak Kasir

DokumenFormatEndpoint
Invoice ResmiPDF A4 berlogo bengkel, detail item, total, PPnGET /kasir/invoices/:id/pdf
Struk KasirPDF Thermal 80mm, ringkas untuk pelangganGET /kasir/invoices/:id/receipt
SPK / Work OrderPDF detail pekerjaan untuk mekanikGET /api/spk/:id/pdf

Invoice dan struk hanya bisa dicetak setelah WO berstatus selesai.

💳 Alur Pembayaran Work Order

flowchart TD START([WO Status: SELESAI]) --> A[Kasir buka WO\nlihat total tagihan] A --> B{Jenis bayar} B -->|Down Payment| C[Input amount DP\nmethod: cash/transfer/dll] B -->|Bayar Penuh| D[Input total amount] B -->|Pelunasan sisa| E[Input sisa amount] C & D & E --> F[POST /api/workorders/:id/payments\ntype: dp / lunas / pelunasan\nmethod: cash/transfer/qris/debit/credit] F --> G{Saldo cukup?} G -->|DP| H[WO paymentStatus: dp\nCatat ke CashDrawer] G -->|Lunas| I[WO paymentStatus: lunas\nCatat ke CashDrawer] I --> J{WO Status = selesai?} J -->|Ya| K[Kasir cetak Invoice\nGET /kasir/invoices/:id/pdf] K --> L[Kasir cetak Struk\nGET /kasir/invoices/:id/receipt] L --> M[SA ubah WO: DISERAHKAN] M --> N([Kendaraan diserahkan ke pelanggan]) style START fill:#fef3c7,stroke:#f59e0b style N fill:#d1fae5,stroke:#10b981

🗄️ Manajemen Laci Kas (Cash Drawer)

flowchart TD A([Shift kasir mulai]) --> B[POST /api/cash-drawers\nisi saldo awal / opening balance] B --> C[Laci KAS: OPEN 📂] C --> D{Transaksi kas} D -->|Pembayaran SPK masuk| E[POST /:id/transactions\ntype: pembayaran_spk] D -->|Kas masuk manual| F[POST /:id/transactions\ntype: kas_masuk] D -->|Kas keluar / pengeluaran| G[POST /:id/transactions\ntype: kas_keluar] E & F & G --> H[Rekap transaksi real-time\nGET /:id/transactions] H --> I([Akhir shift kasir]) I --> J[PATCH /:id/close\nisi saldo aktual / actual cash] J --> K{Rekonsiliasi} K -->|Cocok| L[Status: CLOSED ✅\nDifference = 0] K -->|Selisih| M[Status: CLOSED ⚠️\nCatat perbedaan / difference] L & M --> N[Laporan kas tersimpan\ndi audit log] style A fill:#dbeafe,stroke:#3b82f6 style L fill:#d1fae5,stroke:#10b981 style M fill:#fef3c7,stroke:#f59e0b style N fill:#ede9fe,stroke:#8b5cf6

🏦 Metode Pembayaran yang Didukung

graph LR subgraph METHODS["Metode Bayar"] CASH["💵 CASH\nTunai"] TRANSFER["🏦 TRANSFER\nBank Transfer"] DEBIT["💳 DEBIT CARD\nKartu Debit"] CREDIT["💳 CREDIT CARD\nKartu Kredit"] QRIS["📱 QRIS\nQR Code Payment"] end subgraph TYPES["Tipe Pembayaran"] DP["dp\nUang Muka"] LUNAS["lunas\nBayar Penuh"] PELUNASAN["pelunasan\nLunasi Sisa"] end METHODS -->|Digunakan dalam| TYPES TYPES -->|Update| STATUS subgraph STATUS[Payment Status WO] B[belum - dp - lunas] T[belum - tempo - lunas] end
📦

Inventory & Manajemen Stok

Pengelolaan stok parts/sparepart per cabang, mutasi stok, transfer antar cabang, dan peringatan stok minimum. Setiap gerakan stok tercatat sebagai StockMovement.

Multi-Cabang Stock Tracking Low-Stock Alert Transfer

📊 5 Tipe Stock Movement

TipeSumberEfek
masukPO diterima dari supplierStok bertambah (+qty)
keluarPemakaian di Work Order / parts requestStok berkurang (-qty)
adjustmentKoreksi manual oleh gudangBisa +/- sesuai koreksi
transfer_inMenerima stok dari cabang lainStok bertambah (+qty)
transfer_outMengirim stok ke cabang lainStok berkurang (-qty)

⚠️ Low-Stock Alert

  • Setiap part memiliki nilai minStock yang diset per cabang di BranchStock.
  • Setiap kali stok bergerak, sistem cek: jika stock <= minStock, part masuk daftar alert.
  • Alert tampil di Dashboard via endpoint GET /api/parts/low-stock.
  • Gudang wajib menindaklanjuti dengan buat Purchase Request ke supplier.
  • Setiap StockMovement menyimpan nilai stockAfter untuk audit trail historis.

🔄 Transfer Antar Cabang — 5 Status

StatusArtinya
pendingPermintaan transfer dikirim, menunggu persetujuan cabang tujuan
approvedManager cabang tujuan menyetujui transfer
in_transitBarang sedang dalam perjalanan ke cabang tujuan
receivedBarang diterima, stok kedua cabang diperbarui otomatis
rejectedPermintaan ditolak oleh cabang tujuan

📊 Alur Pergerakan Stok (Stock Movement)

flowchart TD subgraph MASUK[STOK MASUK] M1["Pembelian PO diterima\ntype: masuk\nRef: PO-XXXX"] M2["Transfer diterima\ntype: transfer_in\nRef: TRF-XXXX"] M3["Adjustment positif\ntype: adjustment\n(koreksi stok)"] end subgraph KELUAR[STOK KELUAR] K1["Pemakaian di Work Order\ntype: keluar\nRef: SPK-XXXX"] K2["Transfer dikirim\ntype: transfer_out\nRef: TRF-XXXX"] K3["Adjustment negatif\ntype: adjustment\n(susut / rusak)"] end MASUK --> LEDGER KELUAR --> LEDGER subgraph LEDGER["BranchStock Ledger"] BS["BranchStock\nbranchId + partId\nstock (running total)\nupdatedAt"] SM["StockMovement Log\nSetiap transaksi tercatat\nstockAfter = saldo akhir"] BS --> CHECK["Cek minStock\nJika stock ≤ minStock → Alert"] CHECK --> ALERT["Dashboard Alert\nGET /api/parts/low-stock"] end style MASUK fill:#d1fae5,stroke:#10b981 style KELUAR fill:#fee2e2,stroke:#ef4444 style LEDGER fill:#f8fafc,stroke:#94a3b8

🔄 Transfer Stok Antar Cabang + Approval

flowchart TD A([Gudang Cabang A]) --> B[POST /api/stock/transfers\nFrom: Cabang A, To: Cabang B\nPart + Qty + Notes] B --> C[Status: PENDING\nMenunggu persetujuan] C --> D{Manager / Admin Cabang B} D -->|Setuju| E[PATCH transfers/:id/approve\nStatus: APPROVED] D -->|Tolak| F[PATCH status: REJECTED] E --> G[Status: IN_TRANSIT\nDalam perjalanan] G --> H[Barang fisik dikirim] H --> I{Cabang B terima barang} I -->|Barang tiba| J[PATCH transfers/:id/receive\nStatus: RECEIVED ✅] J --> K[StockMovement TRANSFER_OUT\ndi Cabang A\n−qty] K --> L[StockMovement TRANSFER_IN\ndi Cabang B\n+qty] L --> M[BranchStock update\nboth branches] F --> N([Gudang A notifikasi ditolak]) style A fill:#dbeafe,stroke:#3b82f6 style M fill:#d1fae5,stroke:#10b981 style F fill:#fee2e2,stroke:#ef4444
🛒

Purchase Order & Purchase Request

Pengelolaan pembelian parts/sparepart ke supplier — dari permintaan pembelian (PR) oleh gudang/mekanik, approval, pembuatan PO, pengiriman, penerimaan, hingga pembayaran ke supplier.

Procurement Multi-Level Approval Stock Integration Supplier

📋 Purchase Request (PR)

  • 1
    Gudang atau mekanik buat PR berisi daftar part yang dibutuhkan beserta alasan.
  • 2
    PR masuk ke manager/admin untuk disetujui atau ditolak.
  • 3
    PR yang disetujui dijadikan dasar pembuatan Purchase Order ke supplier.
  • !
    PR yang ditolak harus dibuat ulang — tidak dapat diaktifkan kembali.
Status PRArtinya
pendingMenunggu persetujuan manager
approvedDisetujui, siap dibuatkan PO
rejectedDitolak dengan catatan alasan
fulfilledBarang sudah diterima dari supplier

🛒 Purchase Order (PO) — 5 Status

StatusArtinyaBisa Diedit?
draftSedang disiapkan oleh gudangYa
dikirimSudah dikirim ke supplierTidak
diterimaBarang tiba, stok otomatis bertambahTidak
dibayarPembayaran ke supplier lunasTidak
batalPO dibatalkanTidak

📦 Integrasi dengan Stok

  • Saat PO berstatus diterima via POST /purchase-orders/:id/receive, sistem otomatis membuat StockMovement tipe masuk untuk setiap item PO.
  • BranchStock cabang yang menerima akan bertambah sejumlah qty yang diterima.
  • Nilai stockAfter dicatat di setiap StockMovement untuk keperluan audit trail.
  • Jika PO memiliki nominal besar, akan di-routing ke Approval Workflow sebelum bisa dikirim ke supplier.

🛒 Alur Purchase Request → Purchase Order

flowchart TD A([Gudang / Mekanik]) --> B[POST /api/purchase-requests\nDaftar part yang dibutuhkan\n+ alasan kebutuhan] B --> C[PR Status: PENDING\nMenunggu approval] C --> D{Manager / Admin review} D -->|Approve| E[PATCH PR status: APPROVED] D -->|Reject| F[PATCH PR status: REJECTED\ncatatan penolakan] E --> G[Buat Purchase Order\nPOST /api/purchase-orders\nPilih Supplier] G --> H[Tambah PO Items\npart + qty + harga] H --> I[PO Status: DRAFT] I --> J{Cek apakah perlu approval?} J -->|Ya nominal besar| K[Submit ke Approval Workflow\nlihat modul Approval] J -->|Tidak| L[PO Status: DIKIRIM] K --> APPR{Approval} APPR -->|Approved| L APPR -->|Rejected| M[PO Status: BATAL] L --> N[PO dikirim ke Supplier] N --> O[Supplier kirim barang] O --> P[POST /api/purchase-orders/:id/receive\nPO Status: DITERIMA] P --> Q[StockMovement: type MASUK\nBranchStock tambah qty per part] Q --> R[Proses pembayaran ke supplier] R --> S[PATCH PO status: DIBAYAR] style A fill:#dbeafe,stroke:#3b82f6 style S fill:#d1fae5,stroke:#10b981 style F fill:#fee2e2,stroke:#ef4444 style M fill:#fee2e2,stroke:#ef4444

📊 Status Flow Purchase Order

flowchart LR START([PO Dibuat\nGudang]) --> draft draft[DRAFT\nSedang disiapkan\nbisa diedit] dikirim[DIKIRIM\nSudah dikirim ke supplier\ntidak bisa diedit] diterima[DITERIMA\nBarang sudah datang\nStok otomatis bertambah] dibayar[DIBAYAR\nPembayaran ke supplier\nlengkap] batal[BATAL\nPO dibatalkan] draft -->|Approve + kirim ke supplier| dikirim draft -->|Batalkan PO| batal dikirim -->|Barang tiba dan diterima| diterima diterima -->|Bayar supplier| dibayar dikirim -->|Supplier tidak bisa supply| batal dibayar --> DONE([Selesai]) style START fill:#f1f5f9,stroke:#94a3b8 style draft fill:#fef3c7,stroke:#f59e0b style dikirim fill:#dbeafe,stroke:#3b82f6 style diterima fill:#ccfbf1,stroke:#14b8a6 style dibayar fill:#d1fae5,stroke:#10b981 style batal fill:#fee2e2,stroke:#ef4444
🔩

Parts Request — Permintaan Parts dari Mekanik

Alur permintaan parts dari mekanik saat mengerjakan Work Order, approval oleh gudang, dan penyerahan fisik parts ke mekanik.

Mekanik Flow Quick Approval Stock Deduction

🔩 4 Status Parts Request

StatusArtinyaAktor
pendingRequest dikirim, menunggu review gudangSistem
approvedGudang menyetujui, sedang menyiapkan partsGudang
rejectedStok tidak ada atau permintaan tidak validGudang
deliveredParts sudah diserahkan fisik ke mekanik, stok terpotongGudang

📋 Aturan Bisnis

  • Setiap request wajib mencantumkan referensi Work Order agar bisa di-tracking per SPK.
  • Gudang memutuskan: jika stok ada → approve; jika tidak ada → reject dan buat PR ke supplier.
  • Saat status berubah ke delivered, sistem otomatis membuat StockMovement tipe keluar — stok cabang berkurang sejumlah qty yang diminta.
  • Mekanik bisa menjeda job (pause) sambil menunggu parts, lalu resume setelah parts diterima.
  • Jika request ditolak, mekanik harus buat request baru dengan justifikasi alternatif.

🔩 Alur Parts Request

flowchart TD A([Mekanik sedang kerjakan SPK]) --> B[POST /api/mekanik/parts-request\nPart + qty + alasan + referensi WO] B --> C[Status: PENDING\nRequest terkirim ke gudang] C --> D{Gudang review} D -->|Stok ada, approve| E[PATCH status: APPROVED] D -->|Stok tidak ada| F[PATCH status: REJECTED\nGudang buat PR ke supplier] E --> G[Gudang siapkan parts] G --> H[Serahkan fisik ke mekanik] H --> I[PATCH status: DELIVERED] I --> J[StockMovement: KELUAR\nBranchStock kurang qty] J --> K[WO Item parts terpenuhi\nMekanik lanjut pengerjaan] style A fill:#ede9fe,stroke:#8b5cf6 style K fill:#d1fae5,stroke:#10b981 style F fill:#fee2e2,stroke:#ef4444

📋 Antrian Mekanik

flowchart TD A[GET /api/mekanik/queue\nDaftar WO yang di-assign] --> B{Filter antrian} B -->|Belum mulai| C[WO Items status: WAITING] B -->|Sedang dikerjakan| D[WO Items status: RUNNING] B -->|Selesai hari ini| E[WO Items status: DONE] C --> F[Mekanik pilih job] F --> G[PATCH /jobs/:id/start\nTimer mulai berjalan ▶️] G --> H{Kendala?} H -->|Ada kendala / tunggu parts| I[PATCH /jobs/:id/pause\nTimer berhenti ⏸️] I --> J[Resolve kendala] J --> G H -->|Tidak ada| K[PATCH /jobs/:id/complete\nCatat waktu aktual ✅] K --> L{Semua job WO selesai?} L -->|Ya| M[WO siap QUALITY CHECK] L -->|Tidak| F
👤

HCM — Human Capital Management

Pengelolaan data karyawan, absensi harian, dan evaluasi performa mekanik melalui sistem KPI yang terukur (SPK selesai, on-time rate, return rate, customer rating, akurasi parts).

Karyawan Absensi KPI Mekanik Multi-Cabang

👤 Status Karyawan

StatusArtinya
aktifKaryawan aktif bekerja, bisa di-assign ke WO dan job
tidak_aktifSementara nonaktif (cuti panjang, dll)
resignKaryawan sudah keluar — soft delete, data historis tetap tersimpan
Status AbsensiKeterangan
hadirCheck-in dan check-out tercatat normal
izinIzin resmi dengan dokumen
sakitSakit dengan/tanpa surat dokter
alphaTidak hadir tanpa keterangan

📊 5 Komponen KPI Mekanik

KomponenCara HitungBobot
SPK DoneJumlah SPK selesai / target bulanProduktivitas
On-Time Rate% job selesai tepat waktu vs estimasiKetepatan
Return Rate% SPK yang kembali karena keluhan (lebih rendah = lebih baik)Kualitas
Customer RatingRata-rata rating dari pelanggan post-serviceKepuasan
Parts Accuracy% request parts yang sesuai tanpa retur/lebihEfisiensi

Total KPI Score dipakai sebagai dasar penghitungan bonusKPI di modul Payroll. Satu record per mekanik per bulan (unique: empId + month).

🔩 Spesialisasi Mekanik

  • Mesin / Engine — overhaul, tune up, timing belt, dll.
  • AC Mobil — service AC, freon, kompresor.
  • Kelistrikan — aki, alternator, kabel, ECU.
  • Body & Cat — ketok, las, pengecatan.
  • General — ganti oli, ban, rem, filter — semua mekanik bisa.

Spesialisasi dipakai SA saat assign mekanik ke WO Item untuk memastikan keahlian yang tepat.

👤 Manajemen Karyawan

flowchart TD A([Admin / Manager]) --> B{Aksi Karyawan} B -->|Rekrut| C[POST /api/employees\nNIK, nama, posisi, dept\ngaji, tanggal bergabung] B -->|Edit data| D[PUT /api/employees/:id] B -->|Nonaktifkan| E[DELETE /api/employees/:id\nstatus tidak_aktif] C --> F[Employee aktif\nstatus AKTIF] F --> G{Status karyawan} G -->|Masih bekerja| H[status AKTIF] G -->|Cuti atau izin| I[Dicatat di Attendance] G -->|Keluar| J[status RESIGN\nsoft delete] F --> SPEC[Tentukan Spesialisasi] subgraph SPESIALIS [Spesialisasi Mekanik] SP1[Mesin / Engine] SP2[AC Mobil] SP3[Kelistrikan] SP4[Body dan Cat] SP5[General] end SPEC --> SP1 style A fill:#dbeafe,stroke:#3b82f6 style F fill:#d1fae5,stroke:#10b981 style J fill:#fee2e2,stroke:#ef4444 style SPEC fill:#f1f5f9,stroke:#94a3b8

⏰ Absensi — Check-in / Check-out

flowchart TD A([Karyawan tiba]) --> B[POST /api/attendance/check-in\nRecord: empId, tanggal, jam masuk] B --> C[Attendance status: HADIR] C --> D([Jam pulang]) D --> E[POST /api/attendance/check-out\nRecord: jam keluar] E --> F{Hitung overtime?} F -->|> jam kerja normal| G[Catat overtime hours] F -->|Tepat waktu| H[Overtime = 0] G & H --> I[Attendance record final\nhadir / izin / sakit / alpha] J[Admin koreksi] -->|PUT /api/attendance/:id| I subgraph SUMMARY["Status Absensi"] S1["hadir ✅"] S2["izin 📝"] S3["sakit 🏥"] S4["alpha ❌"] end I --> SUMMARY SUMMARY --> K[Dipakai untuk\nhitung Payroll]

📊 KPI Mekanik — Komponen & Kalkulasi

flowchart LR A([POST /api/kpi/calculate\nbulan + tahun]) --> B{Hitung per mekanik} B --> C1["SPK Done / Target\n📋 Produktivitas"] B --> C2["On-Time % / Target\n⏱️ Ketepatan waktu"] B --> C3["Return Rate / Target\n🔄 Tingkat retur"] B --> C4["Customer Rating / Target\n⭐ Kepuasan pelanggan"] B --> C5["Parts Accuracy / Target\n🔩 Akurasi parts"] C1 --> SCORE["Total KPI Score\n(weighted average)"] C2 --> SCORE C3 --> SCORE C4 --> SCORE C5 --> SCORE SCORE --> D{Hasil} D -->|Score ≥ target| E["Bonus KPI\n💰 Untuk Payroll"] D -->|Score < target| F["Perlu coaching\n📝 Catatan supervisor"] SCORE --> G[Simpan ke KPIMechanic\n@@unique empId + month] style SCORE fill:#dbeafe,stroke:#3b82f6 style E fill:#d1fae5,stroke:#10b981 style F fill:#fef3c7,stroke:#f59e0b
📊

Finance & Akuntansi

Sistem akuntansi double-entry lengkap dengan chart of accounts, jurnal, buku besar, periode fiskal, anggaran (budget), aset tetap, dan laporan keuangan otomatis (Balance Sheet, P&L, Trial Balance).

Double Entry Fiscal Period Budget Alert Auto Journal

📒 Prinsip Double-Entry & Status Jurnal

  • Setiap transaksi keuangan dibuat sebagai JournalEntry dengan minimal dua baris (JournalLine): satu sisi Debit, satu sisi Kredit.
  • Sistem menolak jurnal jika total Debit ≠ total Kredit — wajib balance.
StatusArtinya
draftJurnal masih bisa diedit, belum mempengaruhi saldo akun
postedJurnal dikunci, LedgerEntry dibuat, saldo akun diperbarui
voidedJurnal dibatalkan — sistem buat entry reversal otomatis

📅 Fiscal Period — Locking Periode

  • 1
    Admin buat periode fiskal baru setiap bulan — status awal: OPEN.
  • 2
    Selama periode OPEN, jurnal bisa diposting ke periode tersebut.
  • 3
    Akhir bulan: admin tutup periode (PATCH /periods/:id/close).
  • !
    Periode CLOSED tidak bisa dibuka kembali. Jurnal baru tidak dapat diposting ke periode yang sudah tutup — mencegah manipulasi laporan historis.
  • 4
    Sistem otomatis buat closing entries — saldo Revenue dan Expense dipindah ke Retained Earnings.

📊 5 Tipe Akun (Chart of Accounts)

TipeContoh AkunLaporan
assetKas, Bank, Piutang, StokBalance Sheet
liabilityHutang Usaha, Hutang PajakBalance Sheet
equityModal, Retained EarningsBalance Sheet
revenuePendapatan Jasa, Pendapatan PartsP&L / Income Statement
expenseHPP, Gaji, Sewa, ListrikP&L / Income Statement

Akun dengan flag isCashBank = true dipakai sebagai acuan laporan Arus Kas.

📈 Jurnal Otomatis dari Transaksi

  • Pembayaran WO (Kasir) — Debit: Kas/Bank; Kredit: Pendapatan Jasa + Pendapatan Parts.
  • Pembelian PO — Debit: Stok Parts; Kredit: Hutang Usaha / Kas.
  • Penggajian (Payroll) — Debit: Beban Gaji; Kredit: Kas / Hutang Gaji.
  • Penyesuaian Manual — Dibuat langsung oleh akuntan via modul Jurnal.
  • Semua jurnal otomatis dibuat dengan status posted langsung (tanpa melalui draft).

📒 Alur Jurnal Akuntansi Double-Entry

flowchart TD subgraph SOURCES["Sumber Jurnal Otomatis"] WO["Work Order\nPembayaran SPK"] PO_SRC["Purchase Order\nPembelian parts"] PAY["Payroll\nPenggajian"] ADJ["Manual Adjustment\nKoreksi akuntan"] end SOURCES --> CREATE CREATE[POST /api/journals\nEntry: referenceNumber\nmemo + lines\ndebit/credit per akun] CREATE --> VALIDATE{Validasi\nDebit = Kredit?} VALIDATE -->|Tidak balance| ERR[Error: Jurnal ditolak] VALIDATE -->|Balance ✅| DRAFT[Status: DRAFT 📝\nbisa diedit] DRAFT --> PERIOD{Cek Fiscal Period} PERIOD -->|Period OPEN| POST_ACT[POST /api/journals/:id/post\nStatus: POSTED 📌] PERIOD -->|Period CLOSED| BLOCKED[Error: Period sudah tutup] POST_ACT --> LEDGER[Buat LedgerEntry\nper JournalLine\nupdate Account.balance] LEDGER --> REPORT[Laporan Keuangan\nBalance Sheet · P&L · Trial Balance] POST_ACT --> VOID{Perlu dibatalkan?} VOID -->|Ya| VOID_ACT[POST /api/journals/:id/void\nBuat reversal entry otomatis\nStatus: VOIDED] VOID_ACT --> LEDGER2[Reversal - LedgerEntry\nSaldo kembali normal] style ERR fill:#fee2e2,stroke:#ef4444 style BLOCKED fill:#fee2e2,stroke:#ef4444 style POST_ACT fill:#d1fae5,stroke:#10b981 style REPORT fill:#dbeafe,stroke:#3b82f6

📅 Manajemen Periode Fiskal

flowchart LR A([Admin Keuangan]) --> B[POST /api/periods\nBuat periode bulan baru\nstatus: OPEN] B --> C[Periode OPEN 🟢\nJurnal bisa diposting] C --> D[Aktivitas akuntansi\nberjalan normal] D --> E{Akhir periode} E --> F[Review & rekonsiliasi] F --> G[PATCH /api/periods/:id/close\nstatus: CLOSED 🔴] G --> H{Efek tutup periode} H --> I[Jurnal baru tidak bisa\ndiposting ke periode ini] H --> J[Closing entries dibuat\notomatis oleh sistem] H --> K[Laporan final terkunci\nuntuk audit trail] J --> L[Saldo revenue/expense\ndipindah ke Retained Earnings] style C fill:#d1fae5,stroke:#10b981 style G fill:#fee2e2,stroke:#ef4444 style L fill:#dbeafe,stroke:#3b82f6

📈 Chart of Accounts & Laporan Keuangan

graph TD COA["Chart of Accounts\n(Bagan Akun)"] COA --> ASSET["🏦 ASSET\nAktiva / Harta\nisCashBank untuk kas"] COA --> LIABILITY["📋 LIABILITY\nHutang / Kewajiban"] COA --> EQUITY["💎 EQUITY\nModal / Ekuitas"] COA --> REVENUE["💰 REVENUE\nPendapatan"] COA --> EXPENSE["📤 EXPENSE\nBeban / Pengeluaran"] ASSET & LIABILITY & EQUITY --> BS["📊 Balance Sheet\nNeraca Keuangan\nGET /api/finance/balance-sheet"] REVENUE & EXPENSE --> IS["📈 Income Statement\nLaba Rugi\nGET /api/finance/income-statement"] COA --> TB["📋 Trial Balance\nNeraca Saldo\nGET /api/finance/trial-balance"] COA --> CF["💵 Cash Flow\nArus Kas\nGET /api/finance/cashflow"] EXPENSE --> BUD["📊 Budget Tracking\nTarget vs Aktual\nAlert jika > alertAt%"] style BS fill:#dbeafe,stroke:#3b82f6 style IS fill:#d1fae5,stroke:#10b981 style TB fill:#ede9fe,stroke:#8b5cf6 style CF fill:#fef3c7,stroke:#f59e0b style BUD fill:#fee2e2,stroke:#ef4444
🏭

Aset Tetap & Penyusutan

Pencatatan aset tetap bengkel (lift, kompresor, kendaraan operasional, inventaris kantor), perhitungan penyusutan otomatis metode Garis Lurus (Straight-Line), serta pencatatan jurnal akuntansi yang dipicu sistem setiap awal bulan secara otomatis.

Fixed Asset Straight-Line Auto Journal Cron Monthly Disposal

📐 Formula Penyusutan Garis Lurus

Penyusutan per Bulan =
(Harga Perolehan − Nilai Sisa)
Masa Manfaat (tahun) × 12
Contoh — Lift Hidrolik:
Harga Perolehan: Rp 45.000.000
Nilai Sisa: Rp 5.000.000
Masa Manfaat: 10 tahun (120 bulan)
Penyusutan = (45jt − 5jt) / 120 = Rp 333.333/bulan

📒 3 Akun Utama per Aset

FieldAkunTipe
accountIdAset Tetap (1501–1504)Asset
accDepAccountIdAkumulasi Penyusutan (1502)Asset contra
depExpAccountIdBeban Penyusutan (6006)Expense
Kode AkunNama
1501Peralatan Bengkel
1502Akumulasi Penyusutan
1503Kendaraan Operasional
1504Inventaris Kantor
6006Beban Penyusutan
4500Keuntungan Pelepasan Aset
6500Kerugian Pelepasan Aset

⚙️ Aturan Bisnis Penting

  • Penyusutan berjalan otomatis via CRON setiap tanggal 1 untuk semua aset aktif (isActive = true).
  • Penyusutan berhenti ketika bookValue <= salvageValue — nilai buku tidak boleh turun di bawah nilai sisa.
  • Nilai buku (bookValue) = purchaseCost − accumulatedDepreciation, diperbarui setiap bulan.
  • Setiap penyusutan bulanan otomatis membuat JournalEntry dengan tipe depreciation dan status posted.
  • Aset yang di-dispose di-set isActive = false dan disposedAt diisi, penyusutan berhenti otomatis.
  • Disposal aset menghasilkan laba atau rugi pelepasan yang dicatat sebagai jurnal tersendiri.

🏭 Kategori Aset Bengkel

KategoriContoh AsetMasa Manfaat
Peralatan BengkelLift hidrolik, kompresor, scanner ECU7–10 tahun
KendaraanPick-up operasional, motor kurir5–8 tahun
Inventaris KantorKomputer, printer, meja kasir4–5 tahun
BangunanGedung, pos satpam20–40 tahun

🔄 Lifecycle Aset Tetap — Dari Pembelian hingga Disposal

flowchart TD BUY([Admin / Keuangan\nbeli aset baru]) --> REG[Catat di sistem\nPOST /api/fixed-assets\nnama, kategori, tanggal beli\nharga perolehan, nilai sisa\nmasa manfaat, akun] REG --> JE1[Jurnal Pembelian Aset\nDR Aset Tetap = harga perolehan\nCR Kas atau Hutang = harga perolehan] JE1 --> ACTIVE[Aset AKTIF\nisActive = true\nbookValue = purchaseCost] ACTIVE --> CRON{Cron tanggal 1\nsetiap bulan} CRON -->|bookValue lebih dari salvageValue| DEP[Hitung penyusutan bulanan\nhargaPerolehan minus nilaiSisa\ndibagi masaManfaatBulan] CRON -->|bookValue sama dengan salvageValue| STOP[Penyusutan BERHENTI\nnilai buku = nilai sisa\naset tetap aktif] DEP --> JE2[Jurnal Penyusutan Bulanan\nDR Beban Penyusutan 6006\nCR Akumulasi Penyusutan 1502] JE2 --> UPDATE[Update aset\naccumulatedDepreciation tambah\nbookValue kurang] UPDATE --> CRON STOP --> DISPOSE{Keputusan\ndisposal aset} ACTIVE --> DISPOSE DISPOSE -->|Jual aset| SELL[Input harga jual\nPATCH /api/fixed-assets/:id/dispose\nsalePrice] DISPOSE -->|Buang atau rusak| SCRAP[salePrice = 0] SELL --> COMPARE{Bandingkan\nharga jual vs nilai buku} SCRAP --> COMPARE COMPARE -->|Harga jual lebih besar dari nilai buku| PROFIT[Laba Pelepasan Aset\nDR Kas = harga jual\nDR Akumulasi Penyusutan\nCR Aset = harga perolehan\nCR Laba Pelepasan 4500] COMPARE -->|Harga jual lebih kecil dari nilai buku| LOSS[Rugi Pelepasan Aset\nDR Kas = harga jual\nDR Akumulasi Penyusutan\nDR Rugi Pelepasan 6500\nCR Aset = harga perolehan] PROFIT --> CLOSED[Aset NONAKTIF\nisActive = false\ndisposedAt = sekarang] LOSS --> CLOSED style BUY fill:#dbeafe,stroke:#3b82f6 style ACTIVE fill:#d1fae5,stroke:#10b981 style STOP fill:#f1f5f9,stroke:#94a3b8 style PROFIT fill:#d1fae5,stroke:#10b981 style LOSS fill:#fee2e2,stroke:#ef4444 style CLOSED fill:#f1f5f9,stroke:#94a3b8 style JE1 fill:#ede9fe,stroke:#8b5cf6 style JE2 fill:#ede9fe,stroke:#8b5cf6

📊 Simulasi Penyusutan Bulanan — Garis Lurus

flowchart LR subgraph INPUT [Data Aset] A1[purchaseCost\nRp 45.000.000] A2[salvageValue\nRp 5.000.000] A3[usefulLifeYears\n10 tahun = 120 bulan] end subgraph FORMULA [Formula Garis Lurus] F1[Nilai yang Disusutkan\n45jt minus 5jt = Rp 40.000.000] F2[Penyusutan per Bulan\n40jt dibagi 120 = Rp 333.333] F3[Penyusutan per Tahun\n333.333 x 12 = Rp 4.000.000] F1 --> F2 --> F3 end subgraph SCHEDULE [Jadwal Nilai Buku] S1[Bulan 0\nNilai Buku = Rp 45.000.000] S2[Bulan 12 Tahun 1\nNilai Buku = Rp 41.000.000] S3[Bulan 60 Tahun 5\nNilai Buku = Rp 25.000.000] S4[Bulan 120 Tahun 10\nNilai Buku = Rp 5.000.000 STOP] S1 --> S2 --> S3 --> S4 end subgraph JOURNAL [Jurnal per Bulan] J1[DR Beban Penyusutan 6006\nRp 333.333] J2[CR Akumulasi Penyusutan 1502\nRp 333.333] J1 -.->|balance| J2 end INPUT --> FORMULA --> SCHEDULE FORMULA --> JOURNAL style INPUT fill:#dbeafe,stroke:#3b82f6 style FORMULA fill:#fef3c7,stroke:#f59e0b style SCHEDULE fill:#d1fae5,stroke:#10b981 style JOURNAL fill:#ede9fe,stroke:#8b5cf6

💼 Jurnal Pelepasan Aset (Disposal) — Laba vs Rugi

flowchart TD START([Disposal Aset\nContoh: Lift Hidrolik]) --> DATA subgraph DATA [Data Saat Disposal] D1[purchaseCost = Rp 45.000.000] D2[accumulatedDepreciation = Rp 20.000.000] D3[bookValue = Rp 25.000.000] end DATA --> SELL_PRICE[Input Harga Jual\nsalePrice] SELL_PRICE --> CASE{Perbandingan} CASE -->|salePrice = Rp 30.000.000\nlebih besar dari bookValue 25jt| LABA subgraph LABA [Jurnal Laba Pelepasan] L1[DR Kas 1001\nRp 30.000.000] L2[DR Akumulasi Penyusutan 1502\nRp 20.000.000] L3[CR Aset Tetap 1501\nRp 45.000.000] L4[CR Keuntungan Pelepasan 4500\nRp 5.000.000] L1 & L2 --> L3 & L4 end CASE -->|salePrice = Rp 18.000.000\nlebih kecil dari bookValue 25jt| RUGI subgraph RUGI [Jurnal Rugi Pelepasan] R1[DR Kas 1001\nRp 18.000.000] R2[DR Akumulasi Penyusutan 1502\nRp 20.000.000] R3[DR Kerugian Pelepasan 6500\nRp 7.000.000] R4[CR Aset Tetap 1501\nRp 45.000.000] R1 & R2 & R3 --> R4 end LABA --> DONE([isActive = false\ndisposedAt = now]) RUGI --> DONE style LABA fill:#d1fae5,stroke:#10b981 style RUGI fill:#fee2e2,stroke:#ef4444 style DATA fill:#f8fafc,stroke:#94a3b8 style DONE fill:#f1f5f9,stroke:#94a3b8

🗂️ Struktur Data FixedAsset (ERD)

erDiagram FixedAsset { string id PK string branchId FK string name string category date purchaseDate decimal purchaseCost decimal salvageValue int usefulLifeYears string depreciationMethod string accountId FK string accDepAccountId FK string depExpAccountId FK decimal accumulatedDepreciation decimal bookValue boolean isActive datetime disposedAt } Account { string id PK string accountCode string accountName string accountType } JournalEntry { string id PK string entryType string status decimal totalAmount } JournalLine { string journalEntryId FK string accountId FK decimal debitAmount decimal creditAmount } Branch { string id PK string name } Branch ||--o{ FixedAsset : "memiliki" FixedAsset }o--|| Account : "accountId - akun aset" FixedAsset }o--|| Account : "accDepAccountId" FixedAsset }o--|| Account : "depExpAccountId" JournalEntry ||--o{ JournalLine : "terdiri dari" JournalLine }o--|| Account : "posting ke"

⏰ Alur Cron Penyusutan Otomatis (Tanggal 1 Setiap Bulan)

flowchart TD CRON([Cron Job\nTanggal 1 tiap bulan]) --> FETCH[Ambil semua FixedAsset\nisActive = true] FETCH --> LOOP{Iterasi\nper aset} LOOP --> CHECK{bookValue\nlebih dari salvageValue?} CHECK -->|Tidak\nsudah fully deprecated| SKIP[Skip aset ini\ntidak ada jurnal] CHECK -->|Ya| CALC[Hitung penyusutan\nhargaPerolehan minus nilaiSisa\ndibagi totalBulan] CALC --> PERIOD{Fiscal Period\nbulan ini OPEN?} PERIOD -->|Closed| ERR[Log error\nperiod closed\ntidak bisa posting] PERIOD -->|Open| POST[Buat JournalEntry\ntipe: depreciation\nstatus: posted] POST --> LINES[Buat 2 JournalLine\nDR Beban Penyusutan 6006\nCR Akumulasi Penyusutan 1502] LINES --> UPD[Update FixedAsset\naccumulatedDepreciation tambah\nbookValue kurang] UPD --> CAP{bookValue\nkurang dari salvageValue?} CAP -->|Ya| FLOOR[Set bookValue = salvageValue\nnilai buku tidak boleh negatif] CAP -->|Tidak| NEXT FLOOR --> NEXT SKIP --> NEXT ERR --> NEXT NEXT{Aset\nberikutnya?} NEXT -->|Ada| LOOP NEXT -->|Selesai| DONE([Laporan penyusutan\nbulanan selesai]) style CRON fill:#dbeafe,stroke:#3b82f6 style POST fill:#d1fae5,stroke:#10b981 style ERR fill:#fee2e2,stroke:#ef4444 style FLOOR fill:#fef3c7,stroke:#f59e0b style DONE fill:#d1fae5,stroke:#10b981

📈 Dampak Penyusutan ke Laporan Keuangan

flowchart LR DEP[Penyusutan Bulanan\nRp 333.333] DEP --> BS subgraph BS [Balance Sheet - Neraca] B1[Aset Tetap Bruto\nRp 45.000.000 tetap] B2[Akumulasi Penyusutan\nbertambah Rp 333.333] B3[Aset Tetap Netto\nRp 45jt minus Akumulasi] B1 --> B3 B2 --> B3 end DEP --> IS subgraph IS [Income Statement - Laba Rugi] I1[Beban Penyusutan\nbertambah Rp 333.333] I2[Laba Bersih\nberkurang Rp 333.333] I1 --> I2 end DEP --> CF subgraph CF [Cash Flow - Arus Kas] C1[Penyusutan bukan\ntransaksi kas] C2[Ditambahkan kembali\nke arus kas operasi\nnon-cash adjustment] C1 --> C2 end style BS fill:#dbeafe,stroke:#3b82f6 style IS fill:#d1fae5,stroke:#10b981 style CF fill:#fef3c7,stroke:#f59e0b

Approval Workflow — Multi-Level Approval

Sistem persetujuan bertingkat yang fleksibel untuk berbagai modul (PO, PR, Payroll, Transfer Stok). Mendukung konfigurasi level approval, filter berdasarkan nominal, dan jenis approver (user/role).

Multi-Level Configurable Any / All Module Agnostic

🏗️ Konsep ApprovalRule

  • module — modul yang diatur (PO, PR, Transfer, Payroll, Quotation).
  • minAmount / maxAmount — rule berlaku hanya jika nominal transaksi berada di rentang ini. Contoh: rule khusus PO senilai Rp 10jt–50jt.
  • applyToDraft — jika true, approval dibutuhkan bahkan sebelum dokumen di-finalize.
  • isActive — rule bisa dinonaktifkan tanpa dihapus.

🔢 Level Approval & Tipe Persetujuan

requireTypeArtinya
anyCukup satu approver di level ini yang menyetujui — request naik ke level berikutnya
allSemua approver di level ini harus menyetujui sebelum naik ke level berikutnya
  • Approver bisa berupa user spesifik (userId) atau role (semua user dengan role tersebut bisa approve).
  • Jika satu level di-reject, seluruh ApprovalRequest langsung ditolak — tidak perlu konfirmasi level lain.
  • Request yang disetujui semua level → status approved, transaksi asal dapat dilanjutkan.

🔄 Cara Kerja Approval Check

  • 1
    Sistem query GET /api/approval/check?module=PO&amount=25000000 untuk cek apakah transaksi perlu approval.
  • 2
    Jika perlu, sistem buat ApprovalRequest dan set currentLevel = 1.
  • 3
    Approver Level 1 mendapat notifikasi, lalu submit keputusan via POST /api/approval/decide.
  • 4
    Jika disetujui dan masih ada level berikutnya, currentLevel naik ke level 2.
  • 5
    Setelah semua level approve → ApprovalRequest berstatus approved, transaksi dilanjutkan.

🏗️ Konfigurasi Approval Rule

graph TD subgraph RULE["ApprovalRule"] R["module: PO / PR / Payroll / Transfer\nminAmount: 0\nmaxAmount: 50.000.000\napplyToDraft: true\nisActive: true"] end subgraph L1["Level 1 — Supervisor"] LV1["level: 1\nrequireType: any\n(cukup 1 approver)"] LV1 --> APP1A["Approver: User — Manager A"] LV1 --> APP1B["Approver: Role — Supervisor"] end subgraph L2["Level 2 — Manager"] LV2["level: 2\nrequireType: all\n(semua harus approve)"] LV2 --> APP2A["Approver: User — Director"] LV2 --> APP2B["Approver: User — CFO"] end RULE --> L1 --> L2 NOTE["Jika amount > maxAmount\nRule tidak berlaku\nAturan rule lain dipilih"] style RULE fill:#fef3c7,stroke:#f59e0b style L1 fill:#dbeafe,stroke:#3b82f6 style L2 fill:#ede9fe,stroke:#8b5cf6 style NOTE fill:#f1f5f9,stroke:#94a3b8

🔄 Alur Proses Approval Request

sequenceDiagram actor REQ as Requester (Gudang/SA) participant SYS as System participant L1 as Approver Level 1 participant L2 as Approver Level 2 REQ->>SYS: POST /api/approval/check\nmodule=PO&amount=25000000 SYS-->>REQ: requiresApproval: true\nrule: PO > 10jt REQ->>SYS: POST /api/approval/request\ntransactionId, module, ruleId SYS->>SYS: Buat ApprovalRequest\nstatus: pending, currentLevel: 1 SYS-->>L1: Notifikasi: ada request pending alt Level 1 Approve L1->>SYS: POST /api/approval/decide\naction: approved, level: 1 SYS->>SYS: Buat ApprovalDecision\ncurrentLevel → 2 SYS-->>L2: Notifikasi: butuh approval L2 alt Level 2 Approve L2->>SYS: POST /api/approval/decide\naction: approved, level: 2 SYS->>SYS: ApprovalRequest status → APPROVED SYS-->>REQ: ✅ Semua level approved\nTransaksi dilanjutkan else Level 2 Reject L2->>SYS: POST /api/approval/decide\naction: rejected, note SYS->>SYS: ApprovalRequest status → REJECTED SYS-->>REQ: ❌ Ditolak di Level 2 end else Level 1 Reject L1->>SYS: POST /api/approval/decide\naction: rejected, note SYS->>SYS: ApprovalRequest status → REJECTED SYS-->>REQ: ❌ Ditolak di Level 1 end

🗂️ ERD Sistem Approval

erDiagram ApprovalRule { string module decimal minAmount decimal maxAmount boolean applyToDraft boolean isActive } ApprovalLevel { int level string requireType } ApprovalLevelApprover { string type string userId string roleId } ApprovalRequest { string module string transactionId string status int currentLevel } ApprovalDecision { string action string note datetime decidedAt } ApprovalRule ||--o{ ApprovalLevel : "memiliki level" ApprovalLevel ||--o{ ApprovalLevelApprover : "approver" ApprovalRule ||--o{ ApprovalRequest : "digunakan di" ApprovalRequest ||--o{ ApprovalDecision : "keputusan per level"

📋 Modul yang Menggunakan Approval

graph LR APPR["Approval\nWorkflow"] subgraph MODULES["Modul Terintegrasi"] PO["🛒 Purchase Order\nNominal > threshold"] PR["📋 Purchase Request\nTergantung rule"] STOCK["🔄 Stock Transfer\nAntar cabang"] PAYROLL_A["💼 Payroll\nApproval manager"] QUOT["💬 Quotation\nDiskon besar"] end APPR --> PO & PR & STOCK & PAYROLL_A & QUOT subgraph RESULT["Hasil Approval"] APP["✅ APPROVED\nProses dilanjutkan"] REJ["❌ REJECTED\nCatatan penolakan"] end MODULES --> RESULT
🛡️

RBAC — Role-Based Access Control

Sistem izin akses berlapis: role sistem bawaan (admin, manager, sa, mekanik, kasir, gudang, operator) + custom dynamic role per cabang, dengan permission per resource dan override per user (allowedPages/deniedPages).

Security Dynamic Role Per-Resource Per-User Override

🛡️ 3 Lapisan Permission

  • 1
    deniedPages[] — Pemblokiran per user. Jika route ada di sini, akses ditolak meski role punya izin. Dicek pertama kali.
  • 2
    allowedPages[] — Pengecualian per user. Jika route ada di sini, akses diberikan meski role tidak punya izin. Dicek kedua.
  • 3
    Role Permission — Default akses berdasarkan role sistem atau custom role. Dicek terakhir.

🔑 4 Operasi per Resource

OperasiHTTP MethodContoh
canViewGETLihat daftar WO
canCreatePOSTBuat WO baru
canEditPUT / PATCHUpdate status WO
canDeleteDELETEHapus item WO

Permission diset per resource (contoh: workorder, inventory, finance) di RolePermission, baik untuk role sistem maupun custom role.

👥 7 Role Sistem (Built-in)

RoleScopeAkses Utama
adminSemua cabangFull — semua modul
manager1 cabangFull — cabang sendiri
sa1 cabangWO, Quotation, Customer
mekanik1 cabangJob, Parts Request
kasir1 cabangPembayaran, Cash Drawer
gudang1 cabangInventory, PO, Transfer
operator1 cabangBooking, Check-in, Follow-up

🔐 Hirarki Permission Check

flowchart TD REQ([Request dari User]) --> JWT{Verify JWT Token} JWT -->|Invalid / Expired| E401[401 Unauthorized] JWT -->|Valid| USER[Ambil data user\nrole + branchId + custom roleId] USER --> BRANCH{Multi-cabang check} BRANCH -->|Admin| ALL[Akses semua cabang] BRANCH -->|Manager/lainnya| OWN[Hanya akses data\ncabang sendiri] ALL & OWN --> PERMISSION{Cek Permission} PERMISSION --> CHECK1{deniedPages\nmengandung route?} CHECK1 -->|Ya| E403[403 Forbidden] CHECK1 -->|Tidak| CHECK2{allowedPages\nmengandung route?} CHECK2 -->|Ya| ALLOW[✅ Izinkan akses] CHECK2 -->|Tidak| CHECK3{Role system\npunya akses resource?} CHECK3 -->|Ya| ALLOW CHECK3 -->|Tidak| CHECK4{Custom Role\nRolePermission.canView?} CHECK4 -->|Ya| ALLOW CHECK4 -->|Tidak| E403 style E401 fill:#fee2e2,stroke:#ef4444 style E403 fill:#fee2e2,stroke:#ef4444 style ALLOW fill:#d1fae5,stroke:#10b981

👥 Matrix Role × Modul Akses

graph TD subgraph ROLES["Role Sistem (Built-in)"] ADM["🛡️ admin\nFull access semua modul\nSemua cabang"] MGR["📊 manager\nFull access 1 cabang\n(branch-scoped)"] SA_R["🎧 sa\nWork Order + Quotation\nCustomer + Vehicle"] MKN_R["🔧 mekanik\nLihat WO assigned\nJob tracker\nParts request"] KSR_R["💰 kasir\nPembayaran + Invoice\nCash drawer"] GDG_R["📦 gudang\nInventory + Part\nPO + PR + Transfer"] OPR_R["📋 operator\nBooking + Checkin\nFollow-up"] end subgraph CUSTOM["Custom Dynamic Role"] CR["Admin bisa buat\ncustom role per cabang\nSet canView/canCreate\ncanEdit/canDelete\nper resource"] end subgraph OVERRIDE["Per-User Override"] OV["allowedPages[]\nTambah akses khusus\nmeski role tidak punya"] OV2["deniedPages[]\nBlokir akses tertentu\nmeski role boleh"] end ROLES --> CUSTOM --> OVERRIDE
⚙️

Master Data

Data referensi yang menjadi pondasi seluruh transaksi — jasa bengkel, paket servis, parts, supplier, metode bayar, kendaraan, dan pengaturan per cabang.

Reference Data Global + Per-Cabang CRUD

📋 Hierarki Master Jasa

  • 1
    Kategori Jasa — Pengelompokan layanan (Tune Up, AC, Rem, Kelistrikan, dll.).
  • 2
    Master Jasa — Item layanan spesifik dengan harga dasar nasional dan estimasi waktu (estMin).
  • 3
    BranchJasaPrice — Override harga per cabang. Jika ada, harga ini dipakai; jika tidak ada, pakai harga dasar.
  • 4
    Service Package — Bundel jasa + parts dalam satu paket harga (misal: Paket Servis Berkala 10.000 km).

📦 Hierarki Master Parts

  • 1
    Kategori Part — Pengelompokan (Filter, Oli, Kampas Rem, Busi, dll.).
  • 2
    Master Part — Data global part: kode, SKU, harga beli, harga jual, brand, supplier default.
  • 3
    BranchStock — Stok aktual per cabang + minStock alert threshold per cabang.

⚙️ Setting Penting Per Cabang

SettingKonfigurasi
PenomoranPrefix SPK (SPK-JKT-) dan Invoice (INV-JKT-) per cabang
PajakPPN default 11%, bisa dioverride per cabang
Metode BayarAktifkan/nonaktifkan metode per cabang
Jam OperasionalDisimpan sebagai JSON (operHours) per hari
Bay / LiftStatus: available / occupied / maintenance
DepartmentStruktur organisasi per cabang (unique: branchId + name)

🗂️ Peta Master Data

graph TD subgraph SERVICE["Master Jasa & Paket"] KJ["Kategori Jasa\n(Tune Up, AC, dll)"] MJ["Master Jasa\nnama, harga, estMinute"] BJP["BranchJasaPrice\nharga override per cabang"] SP["Service Package\nBundel jasa + parts"] KJ --> MJ --> BJP MJ --> SP end subgraph PART["Master Parts"] KP["Kategori Part\n(Filter, Oli, dll)"] MP["Master Part\ncode, sku, harga beli/jual"] SUP["Supplier\nkontak, term pembayaran"] BS_M["BranchStock\nstok per cabang"] KP --> MP --> BS_M SUP --> MP end subgraph KENDARAAN["Master Kendaraan"] MK["Merk Kendaraan\n(Toyota, Honda, dll)"] VEH["Vehicle\ncustomer vehicle data"] MK --> VEH end subgraph BAYAR["Master Pembayaran"] MB["Metode Bayar\ncash/transfer/qris/dll"] TAX["Pajak\nPPN 11% default"] NUM["Penomoran\nPrefix SPK/Invoice"] end subgraph ORG["Master Organisasi"] DEPT["Department\nper cabang"] BAY_M["Bay / Lift\nstatus available/occupied"] BRANCH_M["Branch / Cabang\npengaturan lengkap"] end

💰 Harga Jasa — Global vs Per-Cabang

flowchart TD F[Admin\nSet harga dasar nasional] --> A G[Manager Cabang\nSet harga override lokal] --> B A[Master Jasa\nharga global default] --> B{Cek BranchJasaPrice\nuntuk cabang ini?} B -->|Ada override| C[Gunakan harga cabang\nharga lokal berlaku] B -->|Tidak ada override| D[Gunakan harga global\nharga nasional berlaku] C & D --> E[Tampil di pilihan jasa\nsaat buat WO atau Quotation] style F fill:#dbeafe,stroke:#3b82f6 style G fill:#ede9fe,stroke:#8b5cf6 style C fill:#d1fae5,stroke:#10b981 style D fill:#fef3c7,stroke:#f59e0b style E fill:#f1f5f9,stroke:#94a3b8
📈

Laporan & Dashboard

Pusat analitik real-time untuk monitoring operasional bengkel — dari SPK harian, utilitas mekanik, stok kritis, perbandingan cabang, hingga laporan keuangan lengkap.

Analytics Real-time Multi-Cabang KPI Dashboard

📊 Dashboard Metrics — GET /api/dashboard

graph TD DASH["Dashboard API\nGET /api/dashboard"] DASH --> O["📋 todaySPK\nJumlah SPK hari ini"] DASH --> P["⚙️ activeWO\nWork Order aktif"] DASH --> Q["⏳ waitingPayment\nMenunggu pembayaran"] DASH --> R["💰 todayRevenue\nPendapatan hari ini"] DASH --> S["📅 monthRevenue\nPendapatan bulan ini"] DASH --> T["⚠️ stockAlerts\nStok di bawah minimum"] DASH --> U["👷 mechanicUtilization\nUtilisasi mekanik %"] DASH --> V["📊 recentTransactions\n10 transaksi terakhir"] DASH --> W["🏢 branchComparison\nPerbandingan antar cabang"] style DASH fill:#ede9fe,stroke:#8b5cf6 style T fill:#fee2e2,stroke:#ef4444 style W fill:#dbeafe,stroke:#3b82f6

📑 Laporan yang Tersedia

graph LR subgraph OPERATIONAL["Laporan Operasional"] R1["📋 SPK Report\nby date/mechanic/status"] R2["💰 Revenue Report\nby period/branch"] R3["🔧 Mechanic KPI\nper bulan"] R4["📦 Stock Report\nmovement audit trail"] end subgraph FINANCIAL["Laporan Keuangan"] R5["📊 Balance Sheet\nNeraca"] R6["📈 Income Statement\nLaba Rugi"] R7["📋 Trial Balance\nNeraca Saldo"] R8["💵 Cash Flow\nArus Kas"] end subgraph DOCUMENTS["Dokumen PDF"] D1["📄 SPK PDF\nSurat Perintah Kerja"] D2["📃 Invoice A4\nFaktur resmi"] D3["🧾 Receipt Thermal\nStruk kasir"] end subgraph AUDIT["Audit & Log"] A1["📝 AuditLog\nCREATE/UPDATE/DELETE\nLOGIN/APPROVE/REJECT"] A2["📬 NotificationLog\nWhatsApp history"] end
🔄

End-to-End — Alur Sistem Lengkap

Gambaran alur transaksi dari pelanggan datang hingga laporan keuangan tercatat secara otomatis.

End-to-End Integrated Flow

🔄 Alur Transaksi End-to-End Bengkel Pintar

flowchart TD CUST(["👤 Pelanggan\nDatang / Booking"]) --> OPR_F["📋 OPERATOR\nBooking → Check-in\nCek data kendaraan"] OPR_F --> SA_F["🎧 SERVICE ADVISOR\nBuat Work Order (SPK)\nAntrean → Diagnosa"] SA_F --> QUOT_FLOW{Perlu\nestimasi?} QUOT_FLOW -->|Ya, bikin Quotation| Q_F["💬 Quotation\nKirim ke pelanggan\nTunggu persetujuan"] QUOT_FLOW -->|Langsung kerjakan| MKN_F Q_F -->|Approved + Convert| MKN_F MKN_F["🔧 MEKANIK\nAmbil antrian job\nKerjakan item per item\nTrack waktu (start/pause/done)"] MKN_F --> PARTS{Butuh\nparts?} PARTS -->|Ada stok| GDG_F["📦 GUDANG\nApprove parts request\nKeluar stok\nStockMovement: keluar"] PARTS -->|Stok habis| PO_F["🛒 PO + PR Flow\nBeli ke supplier\nApproval manager\nTerima barang\nStok bertambah"] PO_F --> GDG_F GDG_F --> MKN_CONT["Mekanik lanjut\npengerjaan"] MKN_CONT --> QC_F QC_F["🔍 QUALITY CHECK\nSA / Kepala Mekanik\ncek hasil pengerjaan"] QC_F -->|Pass ✅| SELESAI_F["✅ WO: SELESAI\nSiap diserahkan"] QC_F -->|Revisi ❌| MKN_F SELESAI_F --> KSR_F["💰 KASIR\nTagih pembayaran\nCash / Transfer / QRIS\nCetak Invoice + Struk"] KSR_F --> LUNAS{Lunas?} LUNAS -->|Ya| SERAHKAN["🚗 SA: DISERAHKAN\nKendaraan ke pelanggan"] LUNAS -->|DP / Tempo| KSR_F SERAHKAN --> AUTO["⚙️ SISTEM OTOMATIS"] AUTO --> JOURNAL_F["📒 Auto Journal Entry\nPendapatan + HPP"] AUTO --> NOTIF_F["📱 Notifikasi WA\nTerima kasih + rating"] AUTO --> FOLLOWUP_F["📬 Jadwal Follow-up\nServis berikutnya"] AUTO --> KPI_F["📊 Update KPI Mekanik\nJob selesai + waktu aktual"] JOURNAL_F --> FIN_F["📊 FINANCE\nLedger update\nBalance Sheet\nP&L Report"] style CUST fill:#dbeafe,stroke:#3b82f6 style SERAHKAN fill:#d1fae5,stroke:#10b981 style FIN_F fill:#d1fae5,stroke:#10b981 style QC_F fill:#ede9fe,stroke:#8b5cf6 style AUTO fill:#fef3c7,stroke:#f59e0b

📋 QA Manual — Bengkel Pintar

Skenario pengujian manual seluruh modul, alur bisnis lintas modul, kondisi batas, dan efek samping yang harus diverifikasi sebelum setiap release.

20
Bagian
60+
Test Case
7
Role diuji
17
WS Events
8
Email Triggers
⚙️

Setup Awal & Prasyarat

Data master dan akun yang harus tersedia sebelum memulai pengujian.

👤 Akun Testing yang Dibutuhkan

RoleEmail ContohAkses Utama
adminadmin@bengkel.testSemua modul
managermanager@bengkel.testSemua kecuali user management
sasa@bengkel.testSPK, Booking, CRM, Quotation
mekanikmekanik@bengkel.testAntrian, Job, Parts request
kasirkasir@bengkel.testPembayaran, Cash drawer
gudanggudang@bengkel.testInventaris, PR approval
operatoroperator@bengkel.testCheck-in, Booking, Status WO

📦 Data Master yang Harus Ada

  • Minimal 1 cabang aktif
  • Minimal 1 Bay tersedia
  • Minimal 1 data Jasa (service type)
  • Minimal 1 data Part dengan stok > 0
  • Minimal 1 Pelanggan + Kendaraan
  • Metode Bayar: Tunai, Transfer, Kartu
  • COA Finance sudah dikonfigurasi
🔐

Autentikasi & Manajemen User

Login, token refresh, reset password, buat user, nonaktifkan akun, rate limiting.

7 Test Case
TC-AUTH-01

Login Valid — redirect sesuai role

Langkah
  1. 1Buka /login, masukkan email dan password valid
  2. 2Klik Login
Expected:
  • Redirect sesuai role: admin/manager → /dashboard, kasir → /kasir, mekanik → /mekanik/antrian
  • Token tersimpan di cookie/storage
  • Header Topbar menampilkan nama dan initials user
TC-AUTH-02

Token Expired → Auto Refresh tanpa logout paksa

  1. 1Login, tunggu 15 menit (access token TTL)
  2. 2Buka halaman baru atau klik navigasi
Expected: Sistem auto-refresh token di background. User tidak perlu login ulang. Halaman berhasil dimuat dengan data baru.
TC-AUTH-03

Forgot Password — email link, token sekali pakai

  1. 1Buka /forgot-password, masukkan email valid
  2. 2Cek inbox email
  3. 3Klik link reset → isi password baru → submit
Expected:
  • Email berisi link reset (berlaku 1 jam)
  • API tidak mengembalikan token di response (security)
  • Token ditandai used: true setelah dipakai — tidak bisa dipakai ulang
  • Semua refresh token lama di-revoke → force re-login di device lain
TC-AUTH-04

Buat User Baru → welcome email + efek ke modul lain

  1. 1Admin → Settings → Users → Tambah User
  2. 2Isi nama, email, role, cabang, password sementara → Simpan
Expected: User muncul di daftar. Email "Selamat Datang" terkirim berisi password sementara. Audit log mencatat.
Efek Lintas Modul:
  • Role mekanik: user muncul di dropdown assign mekanik SPK
  • Role kasir: user bisa membuka shift kasir
TC-AUTH-05

Nonaktifkan User → session di device lain mati

  1. 1User A sedang login di device lain
  2. 2Admin toggle inactive pada User A
Expected: Refresh token dihapus dari DB. Device lain menerima 401 pada request berikutnya → redirect ke /login.
TC-AUTH-06

Rate Limiting Login — 429 setelah 10 percobaan

  1. 1Lakukan 11 kali percobaan login gagal dalam 60 detik
Expected: Request ke-11 mengembalikan 429 Too Many Requests.
🛣️

Golden Path End-to-End

Alur bisnis utama: Booking → Check-in → SPK → Mekanik → Parts → Kasir → Diserahkan. Melibatkan semua modul secara berurutan.

TC-FLOW-0117 StepSemua Role

🔄 Urutan Alur

OPERATOR Buat Booking
              
OPERATOR Check-in (buat SPK, assign bay)
              
SA            Tambah item jasa + part, assign mekanik
              
MEKANIK     Mulai → selesai job
              
GUDANG      Approve parts request (jika perlu)
              
SA/MANAGER Quality check → selesai
              
KASIR        Proses pembayaran lunas
              
KASIR        Status → diserahkan
              
[Efek: stok -1, jurnal keuangan, CRM update, bay free, audit log]

📋 TC-FLOW-01: Detail Step-by-Step

Prasyarat: semua data master ada, semua role tersedia
#RoleAksiExpected & Efek Samping
1operatorBuat booking H-1, customer A, kendaraan B, jam 09:00Status = scheduled. Email konfirmasi terkirim. WS booking:created
2operatorCustomer datang → konfirmasi bookingStatus = confirmed
3operatorCheck-in: pilih pelanggan, kendaraan, odometer, baySPK dibuat SPK-YYYYMM-XXXX, status = antrian. Bay = occupied. lastVisit diperbarui. WS wo:created + bay:occupied
4saTambah item: Tune Up Rp 150.000 + Filter Oli Rp 50.000 qty 1subtotal=200.000, PPn 11%=22.000, total=222.000. Job otomatis untuk item jasa
5saAssign mekanik ke SPKMechanicAssignment dibuat. Job.employeeId terisi
6saStatus → pengerjaanValidasi: semua job harus ada mekanik. WS wo:status_changed
7mekanikKlik "Mulai" pada job Tune UpJob status = running, startedAt terisi. WS wo:job_started
8mekanikKlik "Selesai" pada jobJob = done, actualMinutes dihitung. WS wo:job_done
9saStatus → quality_checkValidasi: semua job harus done. Error jika ada yang belum. WS wo:status_changed
10saStatus → selesaiWS wo:ready_to_pay → kasir. Email: Vehicle Ready + WhatsApp ke customer
11kasirTerima notifikasi → buka daftar antrian bayarSPK muncul dengan total tagihan
12kasirBayar Tunai Rp 222.000paymentStatus = lunas. Journal keuangan dibuat. WS wo:paid. Email: Invoice
13kasirStatus → diserahkanValidasi: harus lunas. totalSpend +222.000. Bay = available. WS bay:free
14adminCek Audit LogSemua aksi tercatat: create WO, status changes, payment, assignment
15adminCek Finance → JurnalJournal entry otomatis dari payment muncul (Dr Kas / Cr Pendapatan)
16adminCek Inventaris → Mutasi StokStok Filter Oli berkurang 1. Movement tipe keluar dibuat
17adminCek CRM → Pelanggan AtotalSpend bertambah, lastVisit diperbarui, riwayat servis muncul
📋

Work Order (SPK) — Detail

Validasi transisi status, kalkulasi total, blokir kondisi tidak valid, cetak PDF.

6 Test Case
TC-WO-01

Transisi Status Tidak Valid

  1. 1Buat SPK status antrian
  2. 2Coba langsung ubah ke diserahkan
Expected: Error 400 — "Status tidak bisa berubah dari antrian ke diserahkan"
TC-WO-02

Blokir quality_check jika Job Belum Selesai

  1. 1SPK status pengerjaan dengan 2 job
  2. 2Mekanik selesaikan hanya 1 job
  3. 3SA ubah status → quality_check
Expected: Error 400 — "X job belum selesai"
TC-WO-03

Blokir diserahkan jika Belum Lunas

  1. 1SPK status selesai, paymentStatus = dp
  2. 2Ubah status → diserahkan
Expected: Error 400 — "Pembayaran harus lunas sebelum kendaraan diserahkan"
TC-WO-04

Hitung Ulang Total saat Item Berubah

PPn tetap 11% dari subtotal. Setiap perubahan memperbarui subtotal, ppn, dan total.
AksiSubtotalPPnTotal
qty=2, price=100rb, disc=0%200.00022.000222.000
Update disc=10%180.00019.800199.800
+ item baru 50rb230.00025.300255.300
Hapus item pertama50.0005.50055.500
TC-WO-05

Status Flow yang Valid

DariKe (boleh)Syarat
antriandiagnosa-
diagnosapengerjaanSemua job punya mekanik
pengerjaanquality_checkSemua job done
quality_checkselesai-
selesaidiserahkanpaymentStatus = lunas
TC-WO-06

Cetak SPK (PDF)

Expected: PDF berisi nomor SPK, tanggal, pelanggan, kendaraan, plat, odometer, keluhan, daftar item (jasa + part), total, area tanda tangan.
💰

Kasir & Pembayaran

Pembayaran penuh, DP, pelunasan, invoice, efek ke keuangan dan CRM.

5 Test Case
TC-KAS-01

Pembayaran Penuh (Lunas)

  1. 1Kasir pilih SPK dari antrian bayar
  2. 2Jumlah = total tagihan, metode: Tunai → Submit
Expected:
  • paymentStatus = lunas
  • Payment record type = full. Invoice record dibuat
  • Journal entry: Dr. Kas / Cr. Pendapatan Jasa + Part + PPN
  • WS wo:paid. Email Invoice ke customer
Efek: Finance → journal otomatis. WO → bisa diserahkan.
TC-KAS-02

DP → Pelunasan (2 Transaksi)

  1. 1Bayar 50% dari total → type = dp
  2. 2Bayar sisa 50% → type = pelunasan
Expected:
  • Setelah DP: paymentStatus = dp. Belum bisa diserahkan
  • Setelah pelunasan: akumulasi = total. Status = lunas
  • Journal entry terpisah untuk masing-masing transaksi
TC-KAS-03

Pembayaran Melebihi Total

  1. 1Masukkan jumlah melebihi total tagihan → submit
Expected: Error 400: "Jumlah melebihi total tagihan" — verifikasi logika bisnis yang berlaku.
TC-KAS-04

Cetak Invoice

Expected: Invoice berisi nomor, tanggal, pelanggan, kendaraan, daftar item, subtotal, PPn, total, metode bayar, tanda terima.
🏧

Cash Drawer & Shift Kasir

Buka/tutup shift, saldo kas, selisih, blokir payment tanpa shift aktif.

4 Test Case
TC-SHIFT-01

Buka Shift

  1. 1Kasir → Shift → Buka Shift, isi saldo awal kas
Expected: Status = open. WS cash:drawer_opened. Hanya 1 shift aktif per branch.
TC-SHIFT-02

Tutup Shift → Hitung Selisih

  1. 1Klik Tutup Shift, isi saldo akhir (hitung fisik)
Expected: Selisih = expectedBalance − closingBalance dicatat. WS cash:drawer_closed. Laporan shift tersedia.
TC-SHIFT-03

Payment Tanpa Shift Aktif

  1. 1Kasir proses pembayaran saat tidak ada shift aktif
Expected: Error 400 — "Tidak ada shift aktif, buka shift terlebih dahulu"
TC-SHIFT-04

Saldo Kas Real-Time saat Shift

Expected: Setiap payment tunai menambah saldo kas real-time. Riwayat transaksi tercatat di shift.
🔩

Mekanik — Antrian & Pengerjaan Job

Isolasi antrian per mekanik, timer pengerjaan, pause/resume, trigger WS.

5 Test Case
TC-MEK-01

Antrian Terisolasi per Mekanik

Expected: Mekanik A hanya melihat job miliknya. Job mekanik B tidak tampil. Status: waiting, running, paused, done terlihat.
TC-MEK-02

Mulai Job → startedAt & WS

  1. 1Klik "Mulai" pada job waiting
Expected: Status = running. startedAt terisi. WS wo:job_started. SA terima notifikasi.
TC-MEK-03

Pause & Resume — Akumulasi Waktu

  1. 1Job running → Pause
  2. 2Resume
Expected: Status: running → paused → running. Waktu aktual dihitung akumulatif (exclude waktu pause).
TC-MEK-04

Selesaikan Job → Trigger wo:ready_to_pay

  1. 1Job terakhir di SPK → Klik Selesai
Expected: Job = done. actualMinutes dihitung. WS wo:job_done. Jika semua job done → WS wo:ready_to_pay ke kasir.
📦

Parts Request (Mekanik → Gudang)

Create, approve/reject, efek ke stok, email notification, low stock trigger.

5 Test Case
TC-PR-01

Buat Parts Request → Email ke Gudang/Manager

Expected: PR status = pending. WS parts:requested → gudang. Email PR Approval ke semua gudang/manager di branch.
Efek: Gudang: muncul di antrian approval.
TC-PR-02

Approve → Stok Berkurang + Low Stock Check

Expected: PR = approved. Stok −qty. Movement tipe keluar. WS parts:approved → mekanik. Jika stok ≤ minStock → WS stock:low + email alert.
TC-PR-03

Reject → Stok Tidak Berubah

Expected: PR = rejected. Stok tidak berubah. WS parts:rejected + alasan ke mekanik.
TC-PR-04

Approve dengan Stok = 0

Expected: Error 400 — "Stok tidak cukup. Tersedia: 0, diminta: 1". PR tetap pending.
📊

Inventaris — Stok, Mutasi, Transfer

Masuk/keluar/adjustment, transfer antar cabang, alert minimum stok.

6 Test Case
TC-INV-01

Penerimaan Stok (Masuk)

Expected: BranchStock +qty. StockMovement tipe masuk. stockAfter terisi nilai baru.
TC-INV-02

Adjustment — Set Nilai Absolut

Stok saat ini = 15, buat adjustment qty = 12
Expected: Stok menjadi 12 (bukan −3). Movement mencatat selisih |12−15|=3 sebagai qty. stockAfter = 12.
TC-INV-03

Keluar Melebihi Stok Tersedia

Stok = 3, mutasi keluar qty = 5
Expected: Error: "Insufficient stock. Current: 3, Requested: 5". Stok tidak berubah.
TC-INV-04

Transfer Stok Antar Cabang (3 Fase)

FaseStatusEfek Stok
Buat transfer Branch A→B qty=5pendingTidak berubah
Admin approveapprovedBranch A −5, movement transfer_out
Branch B konfirmasi terimareceivedBranch B +5, movement transfer_in
🛒

Purchase Order (PO)

Buat, approve, receive, return ke supplier. Efek ke stok dan finance AP.

5 Test Case
TC-PO-01

Alur PO: Draft → Approved → Received

StepStatusEfek
Buat PO + itemdraftdisplayId tergenerate
SubmitsubmittedApproval flow aktif
Manager approveapproved-
Receive barangreceivedStok +qty, Movement masuk, AP bertambah
TC-PO-02

Return ke Supplier

Expected: Return record dibuat. Stok −qty return. AP dikurangi (kredit note). Supplier return rate diperbarui.
👥

CRM — Pelanggan, Kendaraan, Followup

Registrasi, loyalty tier, followup otomatis, STNK expired, service reminder email.

7 Test Case
TC-CRM-01

Registrasi Pelanggan + Kendaraan

Expected: Customer dibuat, loyaltyTier = bronze, totalSpend = 0. Vehicle terhubung ke customer. Kendaraan muncul di dropdown check-in.
TC-CRM-02

totalSpend Update setelah SPK Lunas

Expected: Setelah WO diserahkan: lastVisit diperbarui, totalSpend bertambah, riwayat servis muncul di profil.
TC-CRM-03

Loyalty Tier Naik (Cron)

totalSpendTier
< 1.000.000Bronze
1 jt – 4,9 jtSilver
5 jt – 14,9 jtGold
≥ 15 jtPlatinum
Cron berjalan setiap Senin 06:00. Tier diperbarui otomatis.
TC-CRM-04

Followup Otomatis (Inactive + STNK)

Expected (cron 00:05):
  • Pelanggan tidak berkunjung > 6 bulan → followup tipe inactive_customer
  • Kendaraan STNK expired → followup tipe stnk_expired
TC-CRM-05

Service Reminder Email (Cron 09:00)

Expected: Customer dengan email + lastVisit = 3 bulan lalu → Email Reminder terkirim. Konten: kendaraan, tanggal servis terakhir, rekomendasi.
👔

HCM — Karyawan, Absensi, KPI, Payroll

Tambah karyawan, absensi harian, input KPI, proses payroll dengan jurnal otomatis.

4 Test Case
TC-HCM-01

Tambah Karyawan → Muncul di Dropdown Mekanik

Expected: Employee record dibuat. Jika role mekanik: muncul di dropdown assign mekanik pada SPK.
TC-HCM-02

Absensi Clock In / Clock Out

Expected: Clock In: attendance record dibuat, status = hadir. Clock Out: jam keluar tersimpan, durasi dihitung, keterlambatan dicatat.
TC-HCM-03

Payroll → Jurnal Keuangan Otomatis

Expected: Payroll approved: net = gaji + tunjangan − BPJS − pajak. Journal: Dr. Beban Gaji / Cr. Hutang Gaji.
Efek: Finance → jurnal payroll masuk otomatis.
💼

Keuangan (Finance)

COA, jurnal manual/otomatis, neraca balanced, cashflow, budget vs aktual, fiscal period lock.

8 Test Case
TC-FIN-01

Jurnal Manual — Harus Balanced

Expected: Total Debit = Total Kredit → journal dibuat. Jika tidak balance → error.
Error jika: debit ≠ kredit → "Jurnal tidak balanced"
TC-FIN-02

Jurnal Otomatis dari Payment SPK

Payment lunas Rp 222.000 (Tunai, PPn 22.000)
AkunDebitKredit
Kas / 1100222.000-
Pendapatan Jasa / 4100-porsi jasa
Pendapatan Parts / 4200-porsi parts
PPN Keluaran / 2300-22.000
TC-FIN-03

Neraca — Total Aset = Kewajiban + Ekuitas

Expected: Total Aset = Total Kewajiban + Ekuitas (balanced). Kas = saldo kas aktual. Piutang = tagihan belum lunas.
TC-FIN-04

Fiscal Period Lock

  1. 1Admin tutup periode bulan lalu
  2. 2Coba buat jurnal dengan tanggal bulan tsb
Expected: Error — "Periode sudah ditutup, tidak bisa buat jurnal"
🔔

Notifikasi Real-Time (WebSocket + Email)

17 WS event types, 8 email triggers, koneksi indikator, routing ke role yang tepat.

9 Test Case17 WS Events8 Email

📡 WebSocket Events

EventTriggerTarget
wo:createdCheck-in buat SPKBranch
wo:status_changedStatus transitionBranch
wo:job_startedMekanik mulai jobBranch
wo:job_doneMekanik selesai jobBranch
wo:ready_to_paySemua job donerole:kasir
wo:paidPayment lunasBranch
parts:requestedMekanik buat PRrole:gudang
parts:approvedGudang approve PRrole:mekanik
parts:rejectedGudang reject PRrole:mekanik
booking:createdBuat bookingBranch
bay:occupiedCheck-in assign bayBranch
bay:freeWO diserahkanBranch
stock:lowStok ≤ minStockrole:gudang
stock:transferTransfer stokBranch
cash:drawer_openedBuka shiftBranch
cash:drawer_closedTutup shiftBranch

📧 Email Triggers

EmailTriggerPenerima
WelcomeAdmin buat user baruUser baru
Reset PasswordForgot passwordUser
Booking Conf.Booking dibuatCustomer
InvoicePayment lunasCustomer
Vehicle ReadyWO → selesaiCustomer
Service ReminderCron 09:00, 3 blnCustomer
Low Stock AlertStok ≤ minStockManager/Admin
PR ApprovalMekanik buat PRGudang/Manager
TC-NOTIF-01

WS Connection Indicator

Expected: Dot hijau = WS connected. Dot abu/merah = disconnected. Status terlihat di pojok bell notifikasi.
TC-NOTIF-02

Routing WS ke Role yang Tepat

Expected:
  • wo:ready_to_pay hanya kasir yang terima
  • parts:requested hanya gudang yang terima
  • parts:approved/rejected hanya mekanik yang terima
  • stock:low hanya gudang yang terima
TC-NOTIF-03

Email Booking Confirmation (< 30 detik)

Expected: Email terkirim dalam <30 detik setelah booking dibuat. Konten: tanggal, jam, plat, nama bengkel, alamat.
TC-NOTIF-04

Email Invoice + Vehicle Ready setelah Lunas/Selesai

Expected:
  • Payment lunas → invoice otomatis ke email customer
  • WO → selesai → "Kendaraan Siap" ke email customer
  • Jika customer tidak punya email → tidak error, silent skip
🛡️

RBAC & Kontrol Akses Per Role

Isolasi role, branch scope, custom role, page-level ACL.

5 Test Case

🗂️ Matriks Akses Fitur per Role

Fitur admin manager sa mekanik kasir gudang operator
Buat SPK
Proses Pembayaran
Approve PO
Approve Parts Request
Buat User
Lihat Finance / Jurnal
Mutasi Stok Manual
Dashboard Konsolidasi
TC-RBAC-01

Kasir Tidak Bisa Akses Finance

  1. 1Login kasir → akses URL /finance/jurnal langsung
Expected: Redirect ke kasir atau "Tidak punya akses". Menu Finance tidak tampil di sidebar.
TC-RBAC-02

Mekanik Hanya Lihat Job Sendiri

Expected: Hanya job milik mekanik A yang tampil. Job mekanik B tidak bisa diakses.
TC-RBAC-03

Manager Terbatas pada Branch Sendiri

Expected: Akses data branch lain via URL ditolak. branchScope middleware memfilter otomatis di API.
TC-RBAC-04

Custom Role + Page-Level ACL

Expected (Custom Role "Supervisor"): Sidebar hanya tampil modul yang diizinkan. can('finance', 'view') = false jika tidak diberi permission.
Expected (deniedPages): Admin set deniedPages=['/finance'] pada manager B → akses /finance ditolak meski role normalnya bisa.
🗺️

Matriks Efek Lintas Modul

Setiap aksi kunci dan modul mana saja yang ikut terpengaruh secara otomatis.

Aksi WO Stok Finance CRM Bay WS Event Email Audit
Check-in (buat SPK)CreatelastVisitOccupiedwo:created
Tambah item Part ke SPKRecalc
Assign Mekanik
WO → pengerjaanwo:status_changed
Job mulaistartedAtwo:job_started
Job selesaidonewo:job_done
Semua job selesaiwo:ready_to_pay → kasir
WO → selesaiwo:status_changedVehicle Ready
Payment LunaspayStatusJournaltotalSpendwo:paidInvoice
WO → diserahkanlastVisitFreebay:free
Parts Request createparts:requested → gudangPR Approval
Parts Request approveKeluarparts:approved → mekanik
PO ReceiveMasukAP
Stok ≤ minStockstock:low → gudangLow Stock
Buat Bookingbooking:createdBooking Conf.
Buka Shift Kasircash:drawer_opened
Payroll ApprovedJournal
Buat UserWelcome
Forgot PasswordReset Link

Checklist Regresi Sebelum Release

Minimum skenario yang harus diverifikasi sebelum setiap deployment ke production.

12 Item Wajib

🔴 Wajib Lulus — Core Flow

  • Login semua 7 role berhasil dan redirect ke halaman yang benar
  • Golden Path end-to-end (TC-FLOW-01) berjalan tanpa error
  • Payment lunas → journal keuangan terbuat otomatis
  • Payment lunas → email invoice terkirim (cek inbox atau log)
  • Parts request approve → stok berkurang
  • Stok ≤ minStock → WS event + email alert berjalan

🟠 Wajib Lulus — Security & Integrity

  • WO selesai → email "kendaraan siap" terkirim ke customer
  • Manager hanya lihat data branch sendiri
  • Mekanik hanya lihat job milik sendiri
  • Cash drawer: tidak bisa payment tanpa shift aktif
  • Fiscal period closed → tidak bisa buat jurnal di periode tsb
  • Dashboard angka konsisten dengan laporan detail
Bengkel Pintar ERP — Flow Diagram Documentation
Generated: April 2026 · Stack: Bun + Hono + Next.js 16 + PostgreSQL + Prisma · v2.0
19 Modul · 100+ Endpoint · Multi-Cabang · 7 Role