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 15 App Router, port 4100). 130+ halaman, 5 portal role (main, kasir, sa, mekanik, operator). Semua request ke backend via Authorization: Bearer {token}. Inline CSS, no Tailwind.
  • 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 15 App Router, React 19, Inline CSS (no Tailwind)
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 4100"] 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 (8 Role)"] ADM["🛡️ Admin
Full Access"] MGR["📊 Manager
Branch Scoped"] SA["🎧 Service Advisor
WO + Quotation"] MKN["🔧 Mekanik
Job Execution"] KARU["👷 Karu (Kepala Regu)
Supervisi + QC"] 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 & M7 KARU --> M1 & M4 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 KARU fill:#fef9c3,stroke:#ca8a04 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)

RolePortal / Redirect Setelah Login
admin/dashboard — portal utama (main)
manager/dashboard — portal utama (main)
sa/sa — portal Service Advisor
karu/mekanik — portal mekanik (akses Kepala Regu)
mekanik/mekanik — portal mekanik
kasir/kasir — portal kasir
gudang/dashboard — portal utama
operator/operator — portal 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.

🔗 Daftar Lengkap Auth Endpoints

MethodPathKeterangan
POST/api/auth/loginLogin, dapat accessToken + refreshToken
POST/api/auth/refreshPerbarui accessToken menggunakan refreshToken
POST/api/auth/logoutRevoke refreshToken, akhiri sesi
GET/api/auth/meData user yang sedang login
GET/api/auth/my-permissionsPeta permission lengkap user (resource + action)
POST/api/auth/ws-ticketIssue tiket satu kali (30 detik) untuk upgrade WebSocket
PUT/api/auth/passwordGanti password sendiri (perlu old password)
POST/api/auth/forgot-passwordKirim link reset password ke email
POST/api/auth/reset-passwordSet password baru dengan token dari email

🔑 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 Note over FE,API: Logout FE->>API: POST /api/auth/logout (refreshToken di body) API->>DB: Revoke RefreshToken API-->>FE: 200 OK Note over FE,API: WebSocket Auth FE->>API: POST /api/auth/ws-ticket API-->>FE: ticket (one-time, valid 30s) FE->>API: WS handshake ?ticket=xxx Note over API: Ticket ditukar jadi user context

🔄 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 lunasSA / Karu
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 -->|"PATCH /workorders/:woId/jobs/:jobId/start"| running running -->|"PATCH /workorders/:woId/jobs/:jobId/pause"| paused paused -->|"PATCH /workorders/:woId/jobs/:jobId/start (resume)"| running running -->|"PATCH /workorders/:woId/jobs/:jobId/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\ntipe: reguler atau pre_wo] A --> B[Status: SCHEDULED\nTerjadwal] B --> C[Operator konfirmasi ke pelanggan\nassign mekanik opsional] C --> D[PATCH /api/operator/bookings/:id/confirm\nbody: mechanicIds array\nStatus: CONFIRMED] D --> E{Hari H} E -->|Pelanggan datang| F[PATCH /bookings/:id/status\nstatus: arrived] E -->|Tidak datang| G[PATCH /bookings/:id/status\nstatus: no_show] E -->|Batal| H[PATCH /bookings/:id/status\nstatus: cancelled] F --> I[Check-in kendaraan\nPOST /api/operator/checkin] CI --> I I --> J[Cek data customer dan kendaraan\nKeluhan, odometer, bay] J --> K[WO dibuat otomatis saat check-in\natau SA buat manual\nPOST /api/workorders] K --> LW[PATCH /api/operator/bookings/:id/link-wo\nbody: workOrderId] LW --> 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\nPartId + qty + alasan + workOrderId\nEmail notif ke gudang/manager"] B --> C[Status: PENDING\nRequest terkirim ke gudang\nWS: parts:requested ke gudang] C --> D{Gudang review\nstok tersedia?} D -->|"Stok ada → PATCH /api/mekanik/parts-requests/:id/approve"| E[Status: APPROVED\nNotif ke mekanik] D -->|"Stok habis → PATCH /api/mekanik/parts-requests/:id/reject"| F[Status: REJECTED\nGudang eskalasi ke Purchase Request] F --> PO[Purchase Request, PO, Terima Barang\nStok bertambah] PO --> E E --> G[Gudang siapkan parts fisik] G --> H["PATCH /api/mekanik/parts-requests/:id/deliver\nHanya bisa dari status: APPROVED"] H --> I[Status: DELIVERED\nStockMovement: KELUAR otomatis\nBranchStock kurang qty] I --> 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 style PO fill:#fef3c7,stroke:#f59e0b

📋 Antrian Mekanik

flowchart TD SRC1["WO Job\nsource: wo\nSPK-YYYYMM-XXXX"] --> C SRC2["Booking Job\nsource: booking\nservis terjadwal"] --> C C["GET /api/mekanik/queue\nAntrian job: waiting"] --> F["Mekanik pilih job"] F --> G["PATCH /workorders/:woId/jobs/:jobId/start\nTimer mulai berjalan"] G --> H{Kendala?} H -->|Ada kendala atau tunggu parts| I["PATCH /workorders/:woId/jobs/:jobId/pause\nTimer berhenti"] I --> J["Resolve kendala\natau submit Parts Request"] J --> G H -->|Tidak ada| K["PATCH /workorders/:woId/jobs/:jobId/complete\nCatat waktu aktual"] K --> L{Semua job WO selesai?} L -->|Ya| M["WO siap QUALITY CHECK\nNotif ke kasir: wo:ready_to_pay"] L -->|Tidak| F style SRC1 fill:#ede9fe,stroke:#8b5cf6 style SRC2 fill:#dbeafe,stroke:#3b82f6 style M fill:#d1fae5,stroke:#10b981
👤

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

📖 Skenario Bisnis — End-to-End

Simulasi alur nyata di bengkel: dari pelanggan darurat datang mendadak, servis rutin terencana, penanganan parts habis, pembayaran bertahap, hingga pelanggan yang tidak hadir. Setiap skenario lengkap dengan pelaku, langkah, dan efek sistem.

8
Skenario
8
Pelaku / Role
100+
Step Total
9
Modul Terlibat

👤 Pelaku Eksternal (Sample)

NamaKendaraanSkenario
Pak Ahmad FauziHonda Jazz B-9988-KZ (mesin mati mendadak)SC-01
Pak Budi SantosoToyota Avanza B-1234-AB (servis berkala)SC-02
Ibu Dewi RahayuSuzuki Ertiga D-4421-RH (ganti kampas rem)SC-03
Pak Rudi HermawanToyota Kijang Innova B-7712-MN (overhaul mesin)SC-04
Ibu Sinta WulandariHonda Beat D-5678-XY (ganti oli)SC-05

🏢 Pelaku Internal — Akun Seed

NamaRoleEmail LoginPortal
Dewi Operatoroperatoroperator@bengkelpintar.com/operator
Andi SAsasa@bengkelpintar.com/sa
Siti Kasirkasirkasir@bengkelpintar.com/kasir
Joko Mekanikmekanikmekanik@bengkelpintar.com/mekanik
Agus Prasetyomekanikemployee (no login)
Budi Prayitnokarukaru@bengkelpintar.com/mekanik
Rudi Gudanggudanggudang@bengkelpintar.com/dashboard
Budi Managermanagermanager@bengkelpintar.com/dashboard

Semua akun seed: password password123

🔧 Tanggung Jawab per Role

RoleTanggung Jawab Utama
operatorBooking, check-in kendaraan, follow-up pelanggan
saBuat & kelola SPK, assign mekanik, quality check, serahkan kendaraan
kasirProses pembayaran, kelola shift & laci kas, cetak invoice
mekanikKerjakan job, request parts, track waktu aktual
karuKepala Regu — supervisi, quality check, approve parts request
gudangKelola stok, approve & keluarkan parts, terima barang PO
managerApprove Purchase Order, pantau laporan & KPI seluruh cabang

📋 Ringkasan 8 Skenario

IDNama SkenarioTriggerPelaku UtamaHighlight
SC-01Booking Darurat → SPKAhmad Hidayat — Honda Jazz mogokDewi · Andi · Joko · SitiWalk-in langsung check-in, tanpa booking terjadwal
SC-02SPK Servis Berkala PenuhBudi Santoso — Avanza 35.000 kmDewi · Andi SA · Joko · SitiHappy path lengkap dari booking sampai diserahkan
SC-03SPK + Parts Request & POCitra Dewi — Ertiga, kampas rem habisAndi SA · Joko · Rudi Gudang · Budi Manager · SitiParts habis → PR → PO → terima barang → lanjut servis
SC-04SPK DP + PelunasanDian Purnama — Innova overhaulAndi SA · Joko · SitiDP dulu → pengerjaan → pelunasan → diserahkan
SC-05Booking No Show → Follow-upEko Prasetyo — tidak hadirDewi · SistemNo show → follow-up manual → reschedule
SC-06Kasir — Buka Shift, Bayar, Tutup ShiftHari kerja Siti KasirSiti KasirShift open → 3 pembayaran (cash/transfer/DP) → shift report → tutup
SC-07Gudang — Terima PO, Kelola Stok, Fulfill PRJoko request parts untuk WOJoko Mekanik · Rudi Gudang · Budi ManagerParts request → stok kurang → PR → PO → terima → serahkan ke mekanik
SC-08Finance — Jurnal Manual, Tutup Periode, LaporanBudi Manager buat penyesuaianBudi ManagerJurnal draft → post → cek ledger → tutup periode → cetak P&L
🚨

SC-01: Booking Darurat → SPK

Pelanggan datang mendadak (walk-in) karena kendaraan bermasalah — tidak ada booking sebelumnya. Operator langsung proses check-in dan SA segera buat SPK dengan prioritas antrian lebih cepat.

Darurat / Walk-in Tanpa Booking Check-in Langsung

👥 Pelaku

👤 Pelanggan 📞 Operator 🎧 Service Advisor (SA) 🔧 Mekanik 💰 Kasir

Perbedaan dari Alur Normal

  • Tidak ada booking terjadwal — langsung ke check-in
  • Operator cari data pelanggan via nomor telepon/plat kendaraan
  • Jika pelanggan baru: buat akun customer baru dulu di sistem
  • SPK dibuat langsung dengan status antrian atau bisa langsung diagnosa jika bay tersedia
  • SA mengisi keluhan berdasarkan keterangan pelanggan lisan

📍 Entry Point di Admin

  • A
    Operator buka /operator/checkin → cari pelanggan/kendaraan
  • B
    SA buka /sa/create → buat SPK manual langsung
  • !
    Kedua jalur hasilkan WO yang sama — pilih sesuai siapa yang melayani

🔄 Flow Diagram — SC-01

flowchart TD P(["👤 Pelanggan\nDatang mendadak / telepon darurat"]) --> Q{Pelanggan\nterdaftar?} Q -->|Ya - cari via nama/telp/plat| CI Q -->|Baru - belum terdaftar| NEW["Operator buat customer baru\nPOST /api/customers\nnama, telepon, email"] NEW --> VEH["Daftarkan kendaraan\nPOST /api/vehicles\nplat, merk, model, tahun"] VEH --> CI CI["📋 OPERATOR\nCheck-in kendaraan\nPOST /api/operator/checkin\nodometer + keluhan lisan"] CI --> WO_CREATE["🎧 SERVICE ADVISOR\nBuat Work Order\nSPK nomor otomatis\nstatus: ANTRIAN"] WO_CREATE --> ASSIGN["SA assign Bay (lift/stall)\ndan tentukan tipe servis"] ASSIGN --> ITEM["SA tambah item jasa & parts\nPUT /api/workorders/:id/items"] ITEM --> MECH["SA assign mekanik\nPATCH /api/workorders/:id/mechanics"] MECH --> STATUS1["SA ubah status → DIAGNOSA\nMekanik periksa kendaraan"] STATUS1 --> DIAG{Hasil\ndiagnosa} DIAG -->|Masalah jelas, langsung kerjakan| STATUS2["SA ubah status → PENGERJAAN"] DIAG -->|Perlu estimasi biaya dulu| QUOT["Buat Quotation\nKirim ke pelanggan via WA\nTunggu persetujuan"] QUOT -->|Pelanggan setuju| STATUS2 STATUS2 --> MKN["🔧 MEKANIK\nAmbil antrian job\nKlik Mulai → Selesai tiap job"] MKN --> QC["SA/Karu: QUALITY CHECK\nCek hasil pengerjaan"] QC -->|OK ✅| DONE["WO: SELESAI\nNotif WA ke pelanggan"] QC -->|Perlu revisi| MKN DONE --> PAY["💰 KASIR\nBayar & cetak invoice"] PAY --> HAND["SA: DISERAHKAN\nKendaraan kembali ke pelanggan"] style P fill:#fee2e2,stroke:#ef4444 style HAND fill:#d1fae5,stroke:#10b981 style QUOT fill:#fef3c7,stroke:#f59e0b style QC fill:#ede9fe,stroke:#8b5cf6

📋 SC-01: Step-by-Step Detail

Pelanggan: Pak Ahmad Fauzi · Honda Jazz B-9988-KZ · Mesin mati mendadak di jalan | Prasyarat: Joko tersedia, Bay 2 kosong, Siti sudah buka shift
#PelakuAksiHasil / Efek Sistem
1Pak AhmadDatang ke bengkel, menjelaskan ke Dewi: mesin Jazz tiba-tiba mati di jalan Sudirman, tidak mau nyala
2Dewi (operator)Buka /operator/checkin, cari "Ahmad Fauzi" → ditemukan, pilih Honda Jazz B-9988-KZData pelanggan + kendaraan muncul
2aDewi (operator)(Jika pelanggan baru) Buat customer baru → daftarkan kendaraanCustomer ID + Vehicle ID baru tersimpan di CRM
3Dewi (operator)Input odometer: 48.200 km, keluhan: "Mesin mati mendadak, tidak mau start", Bay 2, submit check-inWO dibuat: SPK-202604-0051, status antrian. vehicle.km=48200, lastVisit diperbarui. WS wo:created
4Andi (sa)Buka SPK-202604-0051, konfirmasi Bay 2 sudah di-assignBay 2 status → occupied. WS bay:occupied
5Andi (sa)Tambah item: jasa "Diagnosa Mesin" Rp 50.000. (item parts menyusul setelah diagnosa)subtotal 50.000, PPn 5.500, total 55.500
6Andi (sa)Assign Joko Mekanik ke SPKMechanicAssignment dibuat, job "Diagnosa Mesin" ter-link ke Joko
7Andi (sa)Ubah status → diagnosaJoko mendapat notifikasi job baru. WS wo:status_changed
8Joko (mekanik)Periksa Jazz: temukan aki lemah + busi mati. Lapor ke Andi. Andi tambah item: Busi x4 Rp 200.000 + Aki Rp 450.000Total diperbarui: Rp 776.250 (termasuk PPn)
9Andi (sa)Ubah status → pengerjaanValidasi mekanik ✓. WS broadcast ke Joko
10Joko (mekanik)Klik Mulai job "Ganti Busi" → kerjakan → klik Selesai. Lanjut "Ganti Aki"Semua job done. actualMinutes dicatat. KPI Joko diperbarui
11Andi (sa)Ubah status → quality_checkValidasi semua job done
12Andi (sa)Test starter Jazz — nyala normal. Ubah status → selesaiWS wo:ready_to_pay ke Siti (kasir). WA ke Pak Ahmad: "Honda Jazz Anda sudah siap"
13Siti (kasir)Pak Ahmad bayar tunai Rp 800.000. Siti input: method=cash, cashReceived=800000Kembalian Rp 23.750. paymentStatus → lunas. CashDrawer +776.250. Jurnal GL otomatis. WS wo:paid
14Andi (sa)Ubah status → diserahkan, serahkan kunci ke Pak AhmadBay 2 → available. WS bay:free. totalSpend Pak Ahmad +776.250. Email invoice terkirim
🔵

SC-02: SPK Servis Berkala — Happy Path Penuh

Skenario standar paling umum: pelanggan sudah booking sebelumnya, datang tepat waktu, servis berjalan lancar, bayar lunas, kendaraan diserahkan. Melibatkan seluruh modul operasional.

Booking Reguler Happy Path Semua Modul

👥 Pelaku

👤 Pelanggan 📞 Operator 🎧 Service Advisor 🔧 Mekanik 💰 Kasir 📊 Manager (monitor)

📅 6 Status SPK Berurutan

StatusAktorSyarat
antrianSistem (saat check-in)
diagnosaSAAda mekanik di-assign
pengerjaanSASemua job punya mekanik
quality_checkSA / KaruSemua job status done
selesaiSAQC lulus
diserahkanSA / KasirpaymentStatus = lunas

⚙️ Efek Sistem Otomatis saat Diserahkan

  • Bay status → available, WS bay:free
  • Jurnal GL: Dr Kas / Cr Pendapatan Jasa + Dr HPP / Cr Persediaan Parts
  • WhatsApp notifikasi terima kasih + rating request
  • Follow-up post_service dijadwalkan otomatis
  • KPI mekanik diperbarui (job selesai + waktu aktual)
  • CRM: totalSpend bertambah, lastVisit diperbarui

🔄 Flow Diagram — SC-02 (Booking → Diserahkan)

flowchart TD FOLLOWUP["📬 Sistem buat follow-up reminder\natau pelanggan minta booking sendiri"] --> BOOK BOOK["📞 OPERATOR\nBuat booking: tanggal, jam, layanan\nPOST /api/operator/bookings\nstatus: SCHEDULED"] BOOK --> CONFIRM["Operator konfirmasi ke pelanggan via WA\nPATCH status: CONFIRMED"] CONFIRM --> HARI_H{Hari H} HARI_H -->|Datang tepat waktu| ARRIVED["PATCH status: ARRIVED\nPelanggan tiba"] HARI_H -->|Batal H-1| CANCEL["PATCH status: CANCELLED"] ARRIVED --> CHECKIN["📋 OPERATOR\nCheck-in kendaraan\nPOST /api/operator/checkin\nodometer + keluhan + bay"] CHECKIN --> SPK["🎧 SERVICE ADVISOR\nSPK: ANTRIAN\nSPK-YYYYMM-XXXX"] SPK --> ADD_ITEM["SA tambah item:\n• Jasa: Tune Up, Ganti Oli, dll\n• Parts: Filter Oli, Busi, dll\nTotal = subtotal + PPn 11%"] ADD_ITEM --> ASSIGN_M["SA assign mekanik\n(bisa lebih dari 1 mekanik)"] ASSIGN_M --> DIAGNOSA["Status → DIAGNOSA\nMekanik periksa kendaraan"] DIAGNOSA --> KERJAKAN["Status → PENGERJAAN\nMekanik mulai job satu per satu"] KERJAKAN --> JOB1["🔧 Mekanik\nKlik Mulai → kerjakan → Selesai\nuntuk setiap job item"] JOB1 --> ALL_DONE{Semua job\nselesai?} ALL_DONE -->|Belum| JOB1 ALL_DONE -->|Ya| QC["Status → QUALITY CHECK\nSA / Karu periksa hasil"] QC -->|Lulus ✅| SELESAI["Status → SELESAI\n📱 WA ke pelanggan: kendaraan siap"] QC -->|Revisi| KERJAKAN SELESAI --> KASIR["💰 KASIR\nBuka antrian bayar\nProses pembayaran\nPOST /api/workorders/:id/payments"] KASIR --> LUNAS["paymentStatus: LUNAS\n📧 Email invoice terkirim\n🖨️ Cetak struk termal"] LUNAS --> SERAHKAN["Status → DISERAHKAN\n🚗 Kendaraan ke pelanggan"] SERAHKAN --> AUTO["⚙️ Efek Otomatis:\nJurnal GL • WA terima kasih • KPI • Follow-up • CRM update"] style BOOK fill:#dbeafe,stroke:#3b82f6 style CANCEL fill:#fee2e2,stroke:#ef4444 style SELESAI fill:#fef3c7,stroke:#f59e0b style SERAHKAN fill:#d1fae5,stroke:#10b981 style AUTO fill:#f0fdf4,stroke:#22c55e

📋 SC-02: Step-by-Step Detail (20 Langkah)

Pelanggan: Pak Budi Santoso · Toyota Avanza B-1234-AB · Servis berkala 35.000 km | Operator: Dewi · SA: Andi · Mekanik: Joko · Kasir: Siti
#PelakuAksiHasil / Efek Sistem
1Dewi (operator)Sistem kirim reminder servis berkala ke Pak Budi via WA (auto follow-up servis_berkala — 35.000 km tercapai)Pak Budi balas mau booking
2Dewi (operator)Buat booking: Pak Budi Santoso, Avanza B-1234-AB, besok jam 09:00, type=reguler, layanan Tune Up + Ganti OliBooking BK-A10023 status scheduled. Email konfirmasi ke Pak Budi
3Dewi (operator)Telepon Pak Budi konfirmasi jadwal → PATCH /bookings/BK-A10023/confirm dengan mechanicIds: [Joko]Status confirmed. Joko ter-assign ke booking
4Pak BudiTiba tepat waktu jam 09:00 membawa Avanza
5Dewi (operator)PATCH status: arrived → check-in: odometer 35.420 km, Bay 3, keluhan "Servis rutin 35.000 km"WO: SPK-202604-0042, status antrian. Bay 3 → occupied. PATCH /bookings/BK-A10023/link-wo menghubungkan booking ke SPK
6Andi (sa)Buka SPK-202604-0042, tambah jasa: Tune Up Rp 200.000 + Ganti Oli Rp 75.000subtotal 275.000, PPn 30.250, total 305.250
7Andi (sa)Tambah parts: Filter Oli 1x Rp 45.000 + Oli Mesin 4L x Rp 85.000subtotal 725.000, PPn 79.750, total 804.750
8Andi (sa)Assign Joko Mekanik ke SPK (sudah di-pre-assign dari booking confirm)MechanicAssignment terkonfirmasi
9Andi (sa)Ubah status → diagnosaJoko mendapat notifikasi job. WS wo:status_changed
10Joko (mekanik)Periksa kondisi Avanza — semua sesuai, tidak ada temuan tambahan. Lapor ke Andi
11Andi (sa)Ubah status → pengerjaanValidasi mekanik di-assign ✓. WS broadcast ke Joko
12Joko (mekanik)Klik Mulai job "Tune Up" di portal mekanik /mekanikJob running, startedAt 09:23. WS wo:job_started
13Joko (mekanik)Selesaikan Tune Up → Selesai. Mulai + selesaikan "Ganti Oli + Filter"Semua job done. Joko: actualMinutes Tune Up=38m, Ganti Oli=22m. KPI diperbarui
14Andi (sa)Ubah status → quality_checkValidasi semua job done
15Andi (sa)Cek oli level, bersih — OK. Ubah status → selesaiWS wo:ready_to_pay → Siti (kasir). WA ke Pak Budi: "Avanza B-1234-AB Anda sudah siap"
16Siti (kasir)Terima notifikasi di portal kasir /kasir, buka antrian bayar SPK-202604-0042Total tagihan Rp 804.750 muncul
17Siti (kasir)Pak Budi bayar tunai Rp 810.000. Input: method=cash, cashReceived=810000Kembalian Rp 5.250. paymentStatus → lunas. CashDrawer Siti +804.750. Jurnal GL: Dr Kas / Cr Pendapatan
18Siti (kasir)Cetak invoice A4 + struk termal untuk Pak BudiPDF invoice INV-SPK-202604-0042. Email invoice ke Pak Budi
19Andi (sa)Ubah status → diserahkan, serahkan kunci + struk ke Pak BudiBay 3 → available. WS bay:free. totalSpend Pak Budi +804.750. lastVisit diperbarui
20SistemAuto: WA terima kasih ke Pak Budi + jadwalkan follow-up post_service (survey 3 hari kemudian oleh Dewi)Follow-up entry dibuat, status pending, assigned ke Dewi
📦

SC-03: SPK dengan Parts Request & Purchase Order

Mekanik menemukan parts yang dibutuhkan tidak tersedia di gudang saat pengerjaan. Flow bercabang ke purchase request → approval manager → beli supplier → terima barang → servis dilanjutkan.

Parts Habis Purchase Request Approval Flow

👥 Pelaku

🎧 Service Advisor 🔧 Mekanik 📦 Admin Gudang 📊 Manager 💰 Kasir

📋 Status Parts Request

StatusArtinya
pendingMekanik submit request, menunggu review gudang
approvedGudang setujui — stok dikeluarkan ke job
rejectedGudang tolak (stok habis / salah part) → eskalasi ke PO
deliveredParts sudah diserahkan ke mekanik, job bisa dilanjutkan

🛒 Alur Purchase Order (jika stok habis)

  • 1
    Admin gudang buat Purchase Request (PR)
  • 2
    Manager approve PR → jadi Purchase Order (PO)
  • 3
    Kirim PO ke supplier, tunggu pengiriman
  • 4
    Terima barang: stok bertambah, biaya dicatat
  • !
    SPK ditahan (on hold) selama menunggu parts tiba

🔄 Flow Diagram — SC-03 (SPK + Parts Request)

flowchart TD SPK_KERJAAN["SPK: PENGERJAAN\nMekanik sedang kerjakan job"] --> NEED{Butuh\nparts tambahan?} NEED -->|Stok tersedia| STOK_OK["Gudang keluarkan parts\nStockMovement: keluar\nStok berkurang"] NEED -->|Stok tidak cukup| PR_CREATE PR_CREATE["🔧 MEKANIK\nBuat Parts Request\nPOST /api/mekanik/parts-requests\nnamapart, qty, urgensi"] PR_CREATE --> GDG_REVIEW["📦 ADMIN GUDANG\nReview parts request\nCek stok tersedia"] GDG_REVIEW -->|Stok ada ✅| PR_APPROVE["Approve parts request\nPATCH status: approved\nKeluarkan stok ke job"] GDG_REVIEW -->|Stok habis ❌| PR_REJECT["Reject parts request\nStatus: rejected\nEskalasi ke Procurement"] PR_REJECT --> PURCHASE_REQ["📦 Admin Gudang\nBuat Purchase Request (PR)\nPOST /api/purchase-requests"] PURCHASE_REQ --> MGR_APPROVE["📊 MANAGER\nReview PR\nPATCH /api/purchase-requests/:id/status"] MGR_APPROVE -->|Reject| PR_REJECT2["PR ditolak — cari alternatif\natau tunggu budget"] MGR_APPROVE -->|Approve| PO_CREATE["Buat Purchase Order\nPOST /api/purchase-orders\nKirim ke supplier"] PO_CREATE --> SUPPLIER["🚚 Supplier proses\nkirim barang"] SUPPLIER --> RECEIVE["Admin Gudang terima barang\nPOST /api/purchase-orders/:id/receive\nStok bertambah ✅"] RECEIVE --> FULFILL["Admin Gudang\nSerahkan parts ke mekanik\nPATCH /api/mekanik/parts-requests/:id/deliver"] PR_APPROVE & FULFILL --> MKN_LANJUT["🔧 MEKANIK\nMenerima parts\nLanjutkan pengerjaan job"] STOK_OK --> MKN_LANJUT MKN_LANJUT --> JOB_DONE["Semua job selesai ✅"] JOB_DONE --> QC_OK["SA: QC → Selesai"] QC_OK --> KASIR["💰 Kasir proses pembayaran"] style PR_REJECT fill:#fee2e2,stroke:#ef4444 style PR_REJECT2 fill:#fee2e2,stroke:#ef4444 style RECEIVE fill:#d1fae5,stroke:#10b981 style KASIR fill:#d1fae5,stroke:#10b981 style MGR_APPROVE fill:#ede9fe,stroke:#8b5cf6

📋 SC-03: Step-by-Step Detail

Pelanggan: Ibu Dewi Rahayu · Suzuki Ertiga D-4421-RH · Ganti kampas rem + service rutin | SA: Andi · Mekanik: Agus · Gudang: Rudi · Manager: Budi · Kasir: Siti
#PelakuAksiHasil / Efek Sistem
1Andi (sa)SPK Ertiga D-4421-RH sudah masuk status pengerjaan. Agus sedang kerjakan servis rutin
2Agus (mekanik)Saat cek rem depan, temukan kampas rem Ertiga sudah tipis 2mm — perlu diganti segera. Lapor ke AndiAndi konfirmasi ke Ibu Dewi via WA, Ibu Dewi setuju
3Andi (sa)Tambah item parts "Kampas Rem Depan Ertiga x2 @ Rp 180.000" ke SPKTotal tagihan bertambah +399.600 (incl. PPn)
4Agus (mekanik)Submit parts request di portal: partId=KAMPAS-ERTIGA-D, qty=2, alasan="Kampas rem tipis 2mm, perlu ganti"Parts request PR-0089 status pending. WS parts:requested → Rudi (gudang). Email ke Rudi + Budi
5Rudi (gudang)Buka antrian parts request, cek stok "Kampas Rem Ertiga" di sistemBranchStock = 0 — tidak bisa approve
6Rudi (gudang)PATCH /api/mekanik/parts-requests/PR-0089/reject → buat Purchase Request ke supplier "Mitra Autosupply", qty=10PR-0089 status rejected. Purchase Request PR-PO-0031 dibuat via POST /api/purchase-requests
7Budi (manager)Terima notifikasi approval PR-PO-0031, review spesifikasi + harga, PATCH /api/purchase-requests/PR-PO-0031/statusapprovedPurchase Request disetujui. Rudi dapat notifikasi untuk buat PO
8Rudi (gudang)Buat PO: POST /api/purchase-orders untuk Mitra Autosupply, kirim ke supplier, update status: PATCH /api/purchase-orders/:id/statusdikirimPO-0031 status → dikirim
9Rudi (gudang)Barang datang keesokan harinya — goods receipt: POST /api/purchase-orders/PO-0031/receive, verifikasi 10 pcs kampas rem sesuai POBranchStock "Kampas Rem Ertiga" +10. StockMovement: masuk. PO status → diterima
10Rudi (gudang)PATCH /api/mekanik/parts-requests/PR-0089/deliver — keluarkan 2 kampas rem ke AgusPR-0089 status delivered. BranchStock -2. StockMovement: keluar. Agus dapat notifikasi "Parts siap"
11Agus (mekanik)Ambil kampas rem dari Rudi, lanjutkan pengerjaan — pasang kampas rem depan kiri & kananJob "Ganti Kampas Rem" → runningdone, actualMinutes=45m
12Andi (sa)Semua job Agus done → QC (cek pengereman) → ubah status → selesaiWS wo:ready_to_pay → Siti. WA ke Ibu Dewi: "Ertiga Anda sudah siap"
13Siti (kasir)Proses pembayaran Ibu Dewi: total termasuk kampas rem + jasa. Method=transfer, reference="TRF-BCA-7712"paymentStatus → lunas. Jurnal GL: Dr Kas / Cr Pendapatan + Dr HPP Rp 270.000 / Cr Persediaan. Email invoice ke Ibu Dewi
💜

SC-04: Pembayaran Bertahap — DP + Pelunasan

Pelanggan tidak bisa bayar lunas di awal. Kasir proses Down Payment (uang muka) untuk memulai pengerjaan, lalu menerima pelunasan sisa tagihan saat kendaraan selesai dan siap diserahkan.

Down Payment Pelunasan Servis Besar

👥 Pelaku

👤 Pelanggan 🎧 Service Advisor 🔧 Mekanik 💰 Kasir

💳 3 Tipe Pembayaran

typeKapan dipakaipaymentStatus WO
dpUang muka pertama, paidAmount < totaldp
lunasBayar langsung penuh sekaliguslunas
pelunasanBayar sisa setelah sebelumnya ada DPlunas

⚠️ Aturan Penting

  • Status diserahkan hanya bisa jika paymentStatus = lunas
  • Kasir harus punya shift aktif (CashDrawer open) untuk proses payment
  • Setiap pembayaran langsung dicatat ke laci kas dan jurnal GL
  • DP bisa dilakukan kapan saja, termasuk sebelum pengerjaan dimulai
  • Kembalian (change) hanya berlaku untuk metode cash

🔄 Flow Diagram — SC-04 (DP + Pelunasan)

flowchart TD SPK_BUAT["SPK dibuat\nTotal besar: overhaul / body repair\ntotal = Rp 3.500.000"] --> KASIR_DP KASIR_DP["💰 KASIR\nPelanggan bayar DP\nPOST /api/workorders/:id/payments\ntype: dp, amount: 1.000.000"] KASIR_DP --> DP_STATUS["paymentStatus: DP\nCashDrawer +1.000.000\nJurnal GL: Dr Kas / Cr Pendapatan (partial)"] DP_STATUS --> KERJAKAN["🔧 PENGERJAAN\nSA ubah status pengerjaan\nMekanik kerjakan"] KERJAKAN --> SELESAI["SPK: SELESAI\nTotal tagihan sudah jelas\nSisa bayar: Rp 2.500.000"] SELESAI --> PELUNASAN["💰 KASIR\nPelanggan bayar sisa\ntype: pelunasan, amount: 2.500.000"] PELUNASAN --> LUNAS["paymentStatus: LUNAS ✅\npaidAmount = total\nCashDrawer +2.500.000\nEmail invoice terkirim"] LUNAS --> SERAHKAN["SA: DISERAHKAN\nValidasi: paymentStatus = lunas ✓\nKendaraan diserahkan"] subgraph BLOCKED["❌ Tidak Bisa Diserahkan"] BLOCK1["Jika paymentStatus = dp\nError 400: harus lunas dulu"] end SELESAI -.->|Coba diserahkan sebelum lunas| BLOCKED style KASIR_DP fill:#dbeafe,stroke:#3b82f6 style PELUNASAN fill:#dbeafe,stroke:#3b82f6 style LUNAS fill:#d1fae5,stroke:#10b981 style SERAHKAN fill:#d1fae5,stroke:#10b981 style BLOCKED fill:#fee2e2,stroke:#ef4444

📋 SC-04: Step-by-Step Detail

Pelanggan: Pak Rudi Hermawan · Toyota Kijang Innova B-7712-MN · Overhaul mesin total Rp 4.200.000 | SA: Andi · Mekanik: Joko + Agus · Kasir: Siti
#PelakuAksiHasil / Efek Sistem
1Andi (sa)SPK dibuat untuk overhaul mesin Innova B-7712-MN — estimasi total Rp 4.200.000, dikerjakan Joko & AguspaymentStatus belum
2Siti (kasir)Siti buka shift pagi: POST /api/kasir/shift/open, saldo awal Rp 500.000CashDrawer status open
3Siti (kasir)Pak Rudi bayar DP tunai Rp 2.000.000. Input: type=dp, method=cash, amount=2000000, cashReceived=2000000paymentStatus → dp. paidAmount=2.000.000. CashDrawer Siti +2.000.000. Jurnal: Dr Kas 2.000.000 / Cr Uang Muka Pelanggan 2.000.000
4Andi (sa)Konfirmasi DP diterima, pindahkan status: antrian → diagnosa → pengerjaan. Assign Joko & AgusJoko dan Agus mendapat notifikasi job overhaul
5Joko + AgusKerjakan overhaul mesin bersama — proses 2-3 hari. Joko: bongkar mesin & ganti bearing. Agus: gasket & assemblySetiap job dicatat startedAt / pausedAt / completedAt. KPI masing-masing tercatat
6Budi Prayitno (karu)Budi (karu) lakukan quality check mesin: cek tekanan kompresi, kebocoran oli, uji coba jalan
6aAndi (sa)QC lulus → ubah status → selesaiWA ke Pak Rudi: "Innova Anda siap, sisa tagihan Rp 2.200.000"
7Pak RudiPak Rudi datang ke bengkel untuk ambil kendaraan
8Siti (kasir)Cek SPK: total 4.200.000, DP sudah 2.000.000, sisa 2.200.000. Input pelunasan: type=pelunasan, method=transfer, amount=2200000, reference="TRF-BRI-08441"paymentStatus → lunas. paidAmount=4.200.000. Jurnal: Dr Transfer Masuk 2.200.000 / Cr Pendapatan 4.200.000 + Dr Uang Muka 2.000.000
9Siti (kasir)Cetak invoice A4 — menampilkan 2 baris pembayaran: DP Rp 2.000.000 (tunai) + Pelunasan Rp 2.200.000 (transfer)Email invoice dikirim ke Pak Rudi
10Andi (sa)Ubah status → diserahkan, serahkan kunci + buku servis ke Pak RudiValidasi lunas ✓. Bay → available. CRM update +4.200.000. WA terima kasih + rating
📵

SC-05: Booking No Show → Follow-up → Reschedule

Pelanggan sudah booking dan dikonfirmasi, tapi tidak hadir di hari yang dijadwalkan. Sistem dan operator menangani dengan follow-up CRM dan menjadwalkan ulang.

No Show Follow-up CRM Reschedule

👥 Pelaku

👤 Pelanggan 📞 Operator ⚙️ Sistem (Otomatis)

📅 Status Booking Terlibat

StatusKapan
confirmedSudah dikonfirmasi ke pelanggan
no_showHari H pelanggan tidak datang — operator update manual

📬 Status Follow-up

StatusKapan
pendingFollow-up masuk antrian
contactedOperator sudah hubungi
scheduledPelanggan mau reschedule
skippedTidak bisa dihubungi / tidak relevan

🤖 Follow-up Otomatis dari Sistem

  • Setelah booking no_show: sistem buat follow-up tipe manual
  • Servis berkala terlewat: trigger servis_berkala otomatis
  • STNK mau habis: trigger stnk_expired otomatis
  • Tidak servis > 3 bulan: trigger inactive_customer
  • Setelah servis selesai: post_service survey kepuasan

🔄 Flow Diagram — SC-05 (No Show → Follow-up)

flowchart TD BOOK["Booking: CONFIRMED\nPelanggan sudah dikonfirmasi\nJadwal: Senin 09:00"] --> HARI{Hari H\nSenin 09:00} HARI -->|Pelanggan tidak datang| NO_SHOW NO_SHOW["OPERATOR\nPATCH booking status: NO_SHOW\nCatat waktu kejadian"] --> FU_CREATE FU_CREATE["Operator buat Follow-up\nPOST /api/operator/followup\ntipe: manual\nalasan: no show pada jadwal sebelumnya"] FU_CREATE --> FU_PENDING["Follow-up: PENDING\nMasuk antrian operator"] FU_PENDING --> CONTACT["OPERATOR\nHubungi pelanggan via WA / Telepon"] CONTACT --> RESULT{Hasil\nkomunikasi} RESULT -->|Tidak bisa dihubungi| SKIP["PATCH status: SKIPPED\nCatat alasan\nFollow-up ditutup"] RESULT -->|Pelanggan mau reschedule| SCHED["PATCH status: SCHEDULED\nAtur tanggal baru\nnextFollowup diset"] RESULT -->|Tidak butuh servis sekarang| COMPL["PATCH status: COMPLETED\nFollow-up selesai"] SCHED --> NEW_BOOK["OPERATOR\nBuat booking baru\nTanggal: Rabu 10:00\nStatus: SCHEDULED"] NEW_BOOK --> CONFIRM2["Konfirmasi ulang ke pelanggan\nSTATUS: CONFIRMED"] CONFIRM2 --> NEXT["Lanjut ke alur booking normal\n(SC-02)"] SKIP --> RETRY["Coba lagi besok\natau tutup permanen\njika 3x tidak respon"] style NO_SHOW fill:#fee2e2,stroke:#ef4444 style SKIP fill:#f1f5f9,stroke:#94a3b8 style NEXT fill:#d1fae5,stroke:#10b981 style SCHED fill:#dbeafe,stroke:#3b82f6

📋 SC-05: Step-by-Step Detail

Pelanggan: Ibu Sinta Wulandari · Honda Beat D-5678-XY · Booking ganti oli Senin tidak hadir, reschedule Rabu | Operator: Dewi Operator
#PelakuAksiHasil / Efek Sistem
1Dewi (operator)Booking Ibu Sinta Wulandari (Honda Beat D-5678-XY) Senin 09:00, type=reguler, layanan Ganti Oli. PATCH /confirm → status confirmedEmail + WA konfirmasi terkirim ke Ibu Sinta
2Dewi (operator)Senin 09:30 — Ibu Sinta belum muncul. Dewi update: PATCH /bookings/:id/statusno_showBooking no_show. Bay tidak jadi dipakai. Dewi catat catatan di sistem
3Dewi (operator)Buat follow-up manual: POST /api/operator/followup, tipe=manual, alasan="Tidak hadir booking Senin 09:00 — Honda Beat D-5678-XY"Follow-up FU-0312 status pending. Masuk antrian CRM Dewi
4Dewi (operator)Senin pukul 12:00 — Dewi hubungi Ibu Sinta via WA
5Ibu SintaBalas WA Dewi: "Maaf Mbak Dewi, tadi ada keperluan mendadak. Bisa Rabu jam 10 ya?"
5aDewi (operator)PATCH follow-up FU-0312: status=contacted, method="whatsapp", notes="Pelanggan minta reschedule Rabu 10:00"Follow-up status contacted
6Dewi (operator)PATCH follow-up FU-0312: status=scheduled, nextFollowup=RabuFollow-up scheduled. Reminder diset untuk Rabu pagi
7Dewi (operator)Buat booking baru: Ibu Sinta, Beat D-5678-XY, Rabu 10:00, Ganti OliBooking BK-A10041 status scheduled
8Dewi (operator)WA konfirmasi ulang ke Ibu Sinta → PATCH /bookings/BK-A10041/confirmStatus confirmed. Email konfirmasi terkirim
9Ibu SintaRabu pagi pukul 09:55 — Ibu Sinta hadir membawa Honda Beat
10Dewi (operator)PATCH booking: arrived → check-in kendaraan, odometer 18.750 km, Bay 1SPK baru SPK-202604-0055 dibuat. Booking BK-A10041 linked ke SPK. Lanjut ke alur SC-02 (Andi, Joko, Siti)
11Sistem3 hari setelah servis selesai: sistem auto-buat follow-up post_service untuk Ibu SintaFollow-up baru status pending masuk antrian Dewi — minta rating kepuasan & reminder servis berikutnya
⚠️ Catatan: Jika Pelanggan Tidak Bisa Dihubungi 3x Tandai follow-up sebagai skipped dengan catatan. Sistem akan mencoba lagi melalui trigger inactive_customer jika pelanggan tidak servis lebih dari 3 bulan. Data riwayat no-show tercatat di CRM untuk analisis loyalitas pelanggan.
💰

SC-06: Kasir — Buka Shift, Proses Pembayaran, Tutup Shift

Skenario hari kerja penuh seorang kasir: membuka laci kas di pagi hari, memproses beberapa pembayaran (tunai, transfer, dan DP), mencatat kas keluar (petty cash), lalu mencetak laporan shift dan menutup laci kas di akhir hari.

Kasir Flow Multi Metode Bayar Shift Report

👥 Pelaku

💰 Siti Kasir 👤 Pelanggan (Ahmad, Budi, Citra)

🔑 Endpoint Kasir Utama

AksiEndpointRole
Buka shiftPOST /api/kasir/shift/openkasir, admin, manager
Tutup shiftPOST /api/kasir/shift/closekasir, admin, manager
Cek shift aktifGET /api/kasir/shift/currentkasir, admin, manager
Riwayat shiftGET /api/kasir/shift/historykasir, admin, manager
Proses pembayaranPOST /api/workorders/:id/paymentskasir, admin, manager
List pembayaran WOGET /api/workorders/:id/paymentskasir, sa, admin, manager
Antrian bayarGET /api/kasir/queuekasir, admin, manager
Kas masuk manualPOST /api/kasir/cash/inkasir, admin, manager
Kas keluar manualPOST /api/kasir/cash/outkasir, admin, manager
Transaksi shift aktifGET /api/kasir/cash/transactionskasir, admin, manager
Ringkasan shiftGET /api/kasir/cash/summarykasir, admin, manager
Invoice PDF (A4)GET /api/kasir/invoices/:woId/pdfkasir, admin, manager
Struk termalGET /api/kasir/invoices/:woId/receiptkasir, admin, manager

💳 Tipe Pembayaran & Metode

typeKapanpaymentStatus WO
lunasBayar penuh sekaliguslunas
dpUang muka, paidAmount < totaldp
pelunasanBayar sisa setelah DPlunas

methodKeterangan
cashTunai — bisa hitung kembalian
transferTransfer bank — wajib isi reference
qrisQRIS / e-wallet
debitKartu debit / kredit

🔄 Flow Diagram — SC-06 (Kasir Daily Flow)

flowchart TD START(["💰 SITI KASIR\nMulai hari kerja"]) --> OPEN OPEN["POST /api/kasir/shift/open\nopeningBalance: 500.000\nshift dibuka"] OPEN --> DRAWER_OPEN["CashDrawer status: open\nWS: cash:drawer_opened\nAudit log dibuat"] DRAWER_OPEN --> ANTRIAN["GET /api/kasir/queue\nLihat antrian WO siap bayar"] ANTRIAN --> PAY1["WO-1: Ahmad Hidayat\nTune Up + Ganti Oli\nTotal Rp 804.750"] PAY1 --> P1["POST /api/workorders/:id/payments\ntype: lunas, method: cash\ncashReceived: 810.000"] P1 --> P1R["paymentStatus: lunas\nKembalian Rp 5.250\nCashDrawer +804.750\nEmail invoice terkirim"] P1R --> PAY2["WO-2: Budi Santoso\nServis Berkala + Parts\nTotal Rp 1.250.000"] PAY2 --> P2["POST /api/workorders/:id/payments\ntype: lunas, method: transfer\nreference: TRF-BCA-8812"] P2 --> P2R["paymentStatus: lunas\nCashDrawer: +1.250.000 (transfer)\nEmail invoice terkirim"] P2R --> PAY3["WO-3: Citra Dewi\nOverhaul Kopling\nTotal Rp 3.200.000"] PAY3 --> P3["POST /api/workorders/:id/payments\ntype: dp, method: cash\namount: 1.500.000"] P3 --> P3R["paymentStatus: dp\npaidAmount: 1.500.000\nSisa: 1.700.000\nCashDrawer +1.500.000"] P3R --> PETTY["Siti catat pembelian ATK\nPOST /api/kasir/cash/out\namount: 45.000, note: ATK kantor"] PETTY --> SUMMARY["GET /api/kasir/cash/summary\nRingkasan transaksi shift aktif"] SUMMARY --> CLOSE["POST /api/kasir/shift/close\nactualCash: hitung fisik uang\nclosingBalance: saldo akhir"] CLOSE --> REPORT["Shift Report:\nTotal masuk: Rp 2.304.750\nTotal keluar: Rp 45.000\nSelisih: 0\nWS: cash:drawer_closed"] style START fill:#d1fae5,stroke:#10b981 style REPORT fill:#d1fae5,stroke:#10b981 style P1R fill:#d1fae5,stroke:#10b981 style P2R fill:#dbeafe,stroke:#3b82f6 style P3R fill:#fef3c7,stroke:#f59e0b

📋 SC-06: Step-by-Step Detail

Kasir: Siti Kasir (kasir@bengkelpintar.com) · Login ke portal /kasir · Tiga transaksi: cash lunas, transfer lunas, DP
#PelakuAksiHasil / Efek Sistem
1Siti (kasir)Login ke http://localhost:4100/kasir dengan kasir@bengkelpintar.com / password123Portal kasir terbuka. Siti melihat status shift belum aktif
2Siti (kasir)Buka laci kas: POST /api/kasir/shift/open dengan body {"openingBalance": 500000}CashDrawer open. WS cash:drawer_opened broadcast. Audit log dicatat. Siti bisa mulai proses transaksi
3Siti (kasir)Lihat antrian: GET /api/kasir/queue — tampil daftar WO siap bayar hari iniMuncul 3 WO siap bayar: Ahmad Hidayat, Budi Santoso, Citra Dewi
4Ahmad HidayatDatang ke kasir untuk bayar Tune Up + Ganti Oli, total Rp 804.750
5Siti (kasir)Proses pembayaran tunai: POST /api/workorders/:woId/payments{"type":"lunas","method":"cash","amount":804750,"cashReceived":810000}paymentStatus WO → lunas. Kembalian Rp 5.250. CashDrawer Siti +804.750. WS wo:paid. Email invoice ke Ahmad
6Siti (kasir)Cetak invoice A4: GET /api/kasir/invoices/:woId/pdf dan struk termal: GET /api/kasir/invoices/:woId/receiptPDF invoice INV-SPK-202604-XXXX dibuka di tab baru. Struk termal dicetak
7Budi SantosoBayar servis berkala total Rp 1.250.000 via transfer BCA
8Siti (kasir)Proses pembayaran transfer: POST /api/workorders/:woId/payments{"type":"lunas","method":"transfer","amount":1250000,"reference":"TRF-BCA-8812"}paymentStatus WO → lunas. CashDrawer: kategori transfer +1.250.000. Email invoice ke Budi
9Citra DewiDatang untuk bayar DP overhaul kopling. Total estimasi Rp 3.200.000, ingin DP Rp 1.500.000
10Siti (kasir)Proses DP: POST /api/workorders/:woId/payments{"type":"dp","method":"cash","amount":1500000,"cashReceived":1500000}paymentStatus WO → dp. paidAmount=1.500.000. CashDrawer +1.500.000. WO belum bisa diserahkan sebelum lunas
11Siti (kasir)Catat pengeluaran petty cash: POST /api/kasir/cash/out{"amount":45000,"note":"Pembelian ATK kantor"}CashTransaction tipe keluar dicatat di shift. CashDrawer -45.000
12Siti (kasir)Akhir hari — cek ringkasan shift: GET /api/kasir/cash/summaryRingkasan: total masuk Rp 3.554.750, total keluar Rp 45.000, kas berjalan Rp 4.009.750
13Siti (kasir)Hitung uang fisik di laci kas. Tutup shift: POST /api/kasir/shift/close{"actualCash":2009750,"closingBalance":2009750,"notes":"Shift sore selesai"}CashDrawer status → closed. Selisih dihitung. WS cash:drawer_closed. Laporan shift bisa diunduh dari GET /api/kasir/shift/history
📦

SC-07: Gudang — Terima PO, Kelola Stok, Fulfill Parts Request

Mekanik sedang mengerjakan SPK dan membutuhkan parts yang stoknya kurang. Skenario lengkap dari request mekanik, gudang cek stok, buat Purchase Request, manager approve, buat PO, supplier kirim barang, gudang terima dan serahkan ke mekanik.

Parts Request Purchase Order Goods Receipt

👥 Pelaku

🔧 Joko Mekanik 📦 Rudi Gudang 📊 Budi Manager

🔩 Endpoint Parts Request

AksiEndpointRole
Submit requestPOST /api/mekanik/parts-requestmekanik, karu
List requestsGET /api/mekanik/parts-requestsmekanik, gudang, manager
Approve (potong stok)PATCH /api/mekanik/parts-requests/:id/approvegudang, admin, manager
RejectPATCH /api/mekanik/parts-requests/:id/rejectgudang, admin, manager
Delivered ke mekanikPATCH /api/mekanik/parts-requests/:id/delivergudang, admin, manager

🛒 Endpoint Purchase & Stok

AksiEndpointRole
Cek stok cabangGET /api/stock/branch-stocksemua
Stok low alertGET /api/parts/low-stocksemua
Buat Purchase RequestPOST /api/purchase-requestsgudang, manager
Approve PRPATCH /api/purchase-requests/:id/statusadmin, manager
Buat POPOST /api/purchase-ordersgudang, manager
Update status POPATCH /api/purchase-orders/:id/statusgudang, manager
Terima barang (GR)POST /api/purchase-orders/:id/receivegudang, manager
Catat gerakan stokPOST /api/stock/movementsgudang, admin, manager

🔄 Flow Diagram — SC-07 (Procurement Chain Lengkap)

flowchart TD MKN(["🔧 JOKO MEKANIK\nSedang kerjakan SPK — butuh\nFilter Bahan Bakar qty=3"]) --> CHECK_STOCK CHECK_STOCK["Joko cek ketersediaan\ndi sistem mekanik portal\n/mekanik/parts-requests"] CHECK_STOCK --> PR_CREATE["POST /api/mekanik/parts-request\npartId, qty=3\nworkOrderId, reason"] PR_CREATE --> PR_PENDING["PartsRequest status: pending\nWS: parts:requested → gudang\nEmail notif ke Rudi"] PR_PENDING --> RUDI_CHECK["📦 RUDI GUDANG\nBuka notifikasi\nGET /api/stock/branch-stock\nCek stok Filter BB"] RUDI_CHECK --> STOK{Stok\ncukup?} STOK -->|Ya| APPROVE["PATCH /api/mekanik/parts-requests/:id/approve\nStok terpotong otomatis\nStockMovement: keluar"] APPROVE --> DELIVER["PATCH /api/mekanik/parts-requests/:id/deliver\nFisik diserahkan ke Joko"] DELIVER --> DONE(["🔧 Joko terima parts\nLanjut kerjakan job"]) STOK -->|Tidak — stok 0| REJECT["PATCH /api/mekanik/parts-requests/:id/reject\nnotes: stok habis"] REJECT --> PR_BUAT["Rudi buat Purchase Request\nPOST /api/purchase-requests\nitems: array of parts + qty"] PR_BUAT --> PR_WAIT["Purchase Request: pending\nNotifikasi ke Budi Manager"] PR_WAIT --> MGR["📊 BUDI MANAGER\nReview PR di admin portal\nPATCH /api/purchase-requests/:id/status\nstatus: approved"] MGR --> PO_BUAT["📦 RUDI GUDANG\nBuat Purchase Order\nPOST /api/purchase-orders\nsupplierId, items, notes"] PO_BUAT --> PO_DRAFT["PO status: draft\nRudi review lalu kirim"] PO_DRAFT --> PO_KIRIM["PATCH /api/purchase-orders/:id/status\nstatus: dikirim\nSupplier terima PO"] PO_KIRIM --> SUPPLIER_KIRIM["🚚 Supplier proses\nkirim barang 1-2 hari"] SUPPLIER_KIRIM --> GR["📦 RUDI GUDANG\nBarang tiba — goods receipt\nPOST /api/purchase-orders/:id/receive"] GR --> STOCK_UP["BranchStock +10 Filter BB\nStockMovement: masuk\nPO status: diterima"] STOCK_UP --> APPROVE style MKN fill:#ede9fe,stroke:#8b5cf6 style DONE fill:#d1fae5,stroke:#10b981 style REJECT fill:#fee2e2,stroke:#ef4444 style GR fill:#dbeafe,stroke:#3b82f6 style MGR fill:#ede9fe,stroke:#8b5cf6

📋 SC-07: Step-by-Step Detail

Mekanik: Joko Mekanik (mekanik@bengkelpintar.com) · Gudang: Rudi Gudang (gudang@bengkelpintar.com) · Manager: Budi Manager (manager@bengkelpintar.com) · Parts: Filter Bahan Bakar
#PelakuAksiHasil / Efek Sistem
1Joko (mekanik)Sedang kerjakan SPK-202604-0073 (servis Dian Purnama). Menemukan Filter Bahan Bakar perlu ganti — cek stok di portal mekanikStok tersedia: 0 pcs di gudang cabang
2Joko (mekanik)Submit parts request: POST /api/mekanik/parts-request{"partId":"<uuid>","partName":"Filter Bahan Bakar","qty":3,"workOrderId":"<woId>","reason":"Filter bahan bakar kotor dan perlu ganti"}PartsRequest PR-0089 status pending. WS parts:requested → Rudi. Email notifikasi ke Rudi Gudang & Budi Manager
3Rudi (gudang)Terima notifikasi, buka admin portal. Cek stok: GET /api/stock/branch-stock?search=Filter+BahanBranchStock = 0 — tidak cukup untuk approve
4Rudi (gudang)Reject parts request: PATCH /api/mekanik/parts-requests/PR-0089/reject{"notes":"Stok habis, akan pengadaan baru"}PR-0089 status → rejected. WS parts:rejected → Joko. Joko tahu harus pause job
5Rudi (gudang)Buat Purchase Request: POST /api/purchase-requests{"items":[{"partId":"<uuid>","name":"Filter Bahan Bakar","qty":10,"reason":"Stok habis, mendesak untuk WO aktif"}],"notes":"Urgent — WO aktif menunggu"}Purchase Request PRQ-0031 status pending. Notifikasi ke Budi Manager
6Budi ManagerReview Purchase Request PRQ-0031 di halaman /inventory. Setuju dengan harga dan kebutuhan. PATCH /api/purchase-requests/PRQ-0031/status{"status":"approved"}PRQ-0031 status → approved. Rudi mendapat notifikasi untuk lanjut buat PO
7Rudi (gudang)Buat Purchase Order: POST /api/purchase-orders{"supplierId":"<uuid>","items":[{"partId":"<uuid>","name":"Filter Bahan Bakar","qty":10,"unitPrice":85000}],"notes":"Urgent untuk WO aktif"}PO-0031 status draft. Total PO Rp 850.000
8Rudi (gudang)Kirim PO ke supplier Mitra Auto: PATCH /api/purchase-orders/PO-0031/status{"status":"dikirim"}PO-0031 status → dikirim. Supplier menerima PO via email
9Rudi (gudang)Barang tiba keesokan harinya — verifikasi 10 pcs Filter BB sesuai PO. Goods Receipt: POST /api/purchase-orders/PO-0031/receiveBranchStock Filter BB: 0 → 10. StockMovement masuk qty=10 dibuat. PO-0031 status → diterima
10Rudi (gudang)Buat parts request baru untuk Joko (approve langsung karena stok sudah ada): PATCH /api/mekanik/parts-requests/PR-0089-NEW/approveStok cukup — approve. BranchStock -3. StockMovement keluar qty=3. WS parts:approved → Joko
11Rudi (gudang)Serahkan 3 Filter BB secara fisik ke Joko: PATCH /api/mekanik/parts-requests/:id/deliverPartsRequest status → delivered. WS parts:delivered → Joko. Joko dapat notifikasi "Parts siap diambil"
12Joko (mekanik)Ambil Filter BB dari Rudi, lanjutkan pengerjaan SPK-202604-0073 — pasang filter baruJob "Ganti Filter BB" → runningdone. Semua job selesai → WS wo:ready_to_pay → Kasir
📊

SC-08: Finance — Jurnal Manual, Tutup Periode, Laporan Keuangan

Manager keuangan membuat jurnal penyesuaian manual, memverifikasi buku besar, menutup periode fiskal bulan berjalan, lalu menghasilkan laporan Laba Rugi dan Neraca untuk dilaporkan ke manajemen.

Double-Entry Fiscal Close P&L & Balance Sheet

👥 Pelaku

📊 Budi Manager

📒 Endpoint Finance — Jurnal

AksiEndpointRole
List jurnalGET /api/finance/journalssemua
Detail jurnalGET /api/finance/journals/:idsemua
Buat jurnal draftPOST /api/finance/journalsadmin, manager, kasir
Edit jurnal draftPUT /api/finance/journals/:idadmin, manager, kasir
Post jurnal (draft→posted)POST /api/finance/journals/:id/postadmin, manager
Void jurnalPOST /api/finance/journals/:id/voidadmin, manager
Hapus jurnal draftDELETE /api/finance/journals/:idadmin, manager
Buku besarGET /api/finance/ledgersemua

📅 Endpoint Periode & Laporan

AksiEndpointRole
List periodeGET /api/finance/periodssemua
Tutup periodePOST /api/finance/periods/closeadmin, manager
Buka kembali periodePUT /api/finance/periods/:id/reopenadmin
Neraca saldoGET /api/finance/reports/trial-balancesemua
Laba Rugi (P&L)GET /api/finance/reports/pnlsemua
Neraca (Balance Sheet)GET /api/finance/reports/balance-sheetsemua
Arus KasGET /api/finance/reports/cash-flowsemua
Export P&L ExcelGET /api/finance/reports/pnl/exportsemua (token-based)
Export Neraca ExcelGET /api/finance/reports/trial-balance/exportsemua (token-based)
Verifikasi GL seimbangGET /api/finance/reports/gl/verifysemua

🔄 Flow Diagram — SC-08 (Finance — Jurnal sampai Laporan)

flowchart TD START(["📊 BUDI MANAGER\nAkhir bulan April 2026\nPerlu jurnal penyesuaian"]) --> JOURNAL_CREATE JOURNAL_CREATE["POST /api/finance/journals\nJurnal draft: Biaya Penyusutan\nDebit: Beban Penyusutan Rp 2.500.000\nKredit: Akumulasi Penyusutan Rp 2.500.000"] JOURNAL_CREATE --> DRAFT["Jurnal status: DRAFT\nDebit = Kredit ✓\nbisa diedit sebelum diposting"] DRAFT --> REVIEW["Budi review jurnal\nGET /api/finance/journals/:id\ncek lines + akun COA"] REVIEW --> POST["POST /api/finance/journals/:id/post\nJurnal: draft → posted\nLedger entries dibuat"] POST --> POSTED["Jurnal status: POSTED\nGL entries aktif\nTidak bisa diedit lagi"] POSTED --> LEDGER["GET /api/finance/ledger\n?accountId=&branchId=&year=2026&month=4\nVerifikasi buku besar"] LEDGER --> VERIFY["GET /api/finance/reports/gl/verify\n?year=2026&month=4\nPastikan total D = total K"] VERIFY --> GL_OK{GL\nSeimbang?} GL_OK -->|Tidak| FIX["Buat jurnal koreksi\natau void jurnal yang salah\nPOST /api/finance/journals/:id/void"] GL_OK -->|Ya| CLOSE_PERIOD["POST /api/finance/periods/close\nbody: branchId, year, month\nTutup periode April 2026"] CLOSE_PERIOD --> CLOSING_JE["Sistem buat jurnal penutup otomatis:\nLaba Ditahan + Reset Akun Nominal"] CLOSING_JE --> REPORTS["Cetak laporan keuangan:"] REPORTS --> PNL["GET /api/finance/reports/pnl\n?year=2026&month=4\nLaporan Laba Rugi"] REPORTS --> BS["GET /api/finance/reports/balance-sheet\n?year=2026&month=4\nNeraca"] REPORTS --> CF["GET /api/finance/reports/cash-flow\n?year=2026&month=4\nArus Kas"] PNL --> EXPORT["GET /api/finance/reports/pnl/export?token=\nDownload Excel P&L"] BS --> EXPORT2["GET /api/finance/reports/trial-balance/export?token=\nDownload Neraca Saldo Excel"] style START fill:#ede9fe,stroke:#8b5cf6 style POSTED fill:#dbeafe,stroke:#3b82f6 style CLOSING_JE fill:#d1fae5,stroke:#10b981 style FIX fill:#fee2e2,stroke:#ef4444 style EXPORT fill:#fef3c7,stroke:#f59e0b style EXPORT2 fill:#fef3c7,stroke:#f59e0b

📋 SC-08: Step-by-Step Detail

Manager: Budi Manager (manager@bengkelpintar.com) · Login ke http://localhost:4100/finance · Periode: April 2026
#PelakuAksiHasil / Efek Sistem
1Budi ManagerLogin ke admin portal, buka modul Finance → Jurnal (/finance/jurnal)Halaman daftar jurnal terbuka. Budi melihat jurnal-jurnal otomatis dari transaksi WO bulan ini
2Budi ManagerBuat jurnal penyesuaian biaya penyusutan peralatan: POST /api/finance/journals — body: {"date":"2026-04-30","description":"Penyusutan peralatan bengkel April 2026","lines":[{"accountId":"<beban-penyusutan-coa-id>","debit":2500000,"credit":0},{"accountId":"<akumulasi-penyusutan-coa-id>","debit":0,"credit":2500000}]}Jurnal JRN-20260430-001 status draft. Validasi: total debit Rp 2.500.000 = total kredit Rp 2.500.000 ✓
3Budi ManagerReview detail jurnal: GET /api/finance/journals/:id — pastikan akun COA benar dan nominal tepatDetail jurnal + lines tampil. Akun debit: "6001 Beban Penyusutan", akun kredit: "1601 Akumulasi Penyusutan Peralatan"
4Budi ManagerPost jurnal ke GL: POST /api/finance/journals/:id/postJurnal status: draftposted. Ledger entries aktif. Tidak bisa diedit lagi. Periode April harus open (belum ditutup)
5Budi ManagerCek buku besar akun 1601: GET /api/finance/ledger?accountId=<id>&year=2026&month=4Ledger menampilkan semua transaksi debit/kredit akun Akumulasi Penyusutan bulan April termasuk jurnal baru
6Budi ManagerVerifikasi GL seimbang: GET /api/finance/reports/gl/verify?year=2026&month=4&branchId=<id>Response: {"balanced":true,"totalDebit":45280000,"totalCredit":45280000} — GL seimbang ✓
7Budi ManagerSebelum tutup periode, lihat Laporan Laba Rugi draft: GET /api/finance/reports/pnl?year=2026&month=4&branchId=<id>P&L bulan April tampil: Pendapatan Jasa Rp 38.500.000, HPP Rp 12.200.000, Laba Bersih Rp 21.800.000 (estimasi)
8Budi ManagerTutup periode April: POST /api/finance/periods/close — body: {"branchId":"<uuid>","year":2026,"month":4}Periode April 2026 → closed. Sistem otomatis buat jurnal penutup: transfer saldo akun nominal ke Laba Ditahan (akun 3002). Tidak ada jurnal baru bisa diposting ke periode ini
9Budi ManagerCetak Laporan Laba Rugi final: GET /api/finance/reports/pnl?year=2026&month=4&branchId=<id>P&L final April 2026 dengan data lengkap termasuk jurnal penutup
10Budi ManagerCetak Neraca: GET /api/finance/reports/balance-sheet?year=2026&month=4&branchId=<id>Balance Sheet April 2026 menampilkan Aset, Liabilitas, dan Ekuitas. Total Aset = Total Liabilitas + Ekuitas ✓
11Budi ManagerExport P&L ke Excel untuk dilaporkan: GET /api/finance/reports/pnl/export?year=2026&month=4&branchId=<id>&token=<jwt>File laba-rugi-2026-04.xlsx terunduh. Format siap kirim ke manajemen pusat
12Budi ManagerExport Neraca Saldo ke Excel: GET /api/finance/reports/trial-balance/export?year=2026&month=4&branchId=<id>&token=<jwt>File neraca-saldo-2026-04.xlsx terunduh. Audit trail lengkap tersedia
⚠️ Catatan: Jurnal Otomatis vs Manual Sebagian besar jurnal GL dibuat otomatis oleh sistem: saat WO dibayar lunas (Dr Kas / Cr Pendapatan), saat parts keluar (Dr HPP / Cr Persediaan), saat kas masuk/keluar kasir, dan saat periode ditutup. Jurnal manual hanya untuk penyesuaian khusus seperti penyusutan, koreksi, dan reklasifikasi yang tidak terjadi dari transaksi operasional.
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