219 lines
16 KiB
Markdown
219 lines
16 KiB
Markdown
# PRD — BerasPro
|
||
|
||
**Produk:** Sembara — Kelola Stok Beras dan Kebutuhan Sembako
|
||
**Versi dokumen:** 1.1 (amandemen hasil sesi grilling 2026-08-25)
|
||
**Tanggal:** 2026-08-25
|
||
**Status:** Aktif
|
||
|
||
> **Catatan amandemen v1.1:** stack frontend diubah menjadi Filament-only; akses data dikunci ke REST API InsForge (JWT per-user); model data disamakan dengan skema live proyek InsForge yang sudah ter-deploy dan berisi data nyata.
|
||
|
||
---
|
||
|
||
## 1. Ringkasan (Overview)
|
||
|
||
Sembara adalah aplikasi web untuk pengelolaan **stok beras** dan **pesanan pelanggan** pada bisnis distribusi/grosir beras. Aplikasi membantu tim operasional mencatat master produk, memantau stok dan mutasinya, memproses sales order dari awal hingga selesai, mencetak surat jalan (delivery note), menangani komplain pelanggan, dan melihat laporan penjualan.
|
||
|
||
Target pengguna: pemilik/usaha beras skala kecil–menengah dengan 2 peran internal — **admin** dan **operator**.
|
||
|
||
## 2. Masalah
|
||
|
||
Pengelolaan bisnis beras yang masih manual (buku/Excel) menimbulkan:
|
||
|
||
- Selisih stok tidak terlacak (tidak ada riwayat mutasi stok).
|
||
- Pesanan hilang atau dobel-proses karena tidak ada alur status yang jelas.
|
||
- Stok tidak otomatis berkurang saat order selesai → oversell.
|
||
- Komplain pelanggan tidak tercatat dan tidak ada penanggung jawab.
|
||
- Laporan penjualan sulit disusun.
|
||
|
||
## 3. Tujuan & Ukuran Sukses
|
||
|
||
| Tujuan | Metrik sukses |
|
||
| --------------------- | ---------------------------------------------------------------------------------------------------------- |
|
||
| Stok akurat | Setiap perubahan stok tercatat di `stock_movements`; selisih fisik vs sistem = 0 pada rekonsiliasi bulanan |
|
||
| Pesanan tertib | 100% order melewati alur status valid (`pending → processing → delivering → completed` / `cancelled`) |
|
||
| Tanpa oversell | Stok hanya berkurang sekali per order (idempotent), duplikat proses ditolak/dilewati |
|
||
| Keluhan terselesaikan | Semua komplain berstatus `open`/`resolved` dengan jejak kepemilikan |
|
||
| Laporan cepat | Ringkasan penjualan & stok tersedia dari halaman Reports tanpa olah manual |
|
||
|
||
## 4. Pengguna & Peran
|
||
|
||
### 4.1 Admin
|
||
|
||
- Mengelola user/operator.
|
||
- Mengelola master produk dan stok (penyesuaian/restok).
|
||
- Menyetujui/memproses/menyelesaikan/membatalkan order; **menghapus order** (soft-delete `deleted_at`).
|
||
- Melihat semua keluhan dan menutupnya.
|
||
- Mengakses laporan, log aktivitas, dan manajemen user.
|
||
|
||
### 4.2 Operator
|
||
|
||
- Membuat dan mengubah pesanan.
|
||
- Mengelola data pelanggan.
|
||
- Menutup keluhan **miliknya sendiri saja** (validasi kepemilikan `created_by`).
|
||
- Tidak dapat menghapus order, mengelola user, mengubah master produk/stok, atau mengakses laporan.
|
||
|
||
### 4.3 Pelanggan (indirek)
|
||
|
||
Tidak login. Datanya dikelola operator sebagai `customers`; komplain atas nama pelanggan dicatat lewat modul Complaints.
|
||
|
||
## 5. Fitur & Kebutuhan Fungsional
|
||
|
||
### 5.1 Autentikasi & Otorisasi
|
||
|
||
- **F1 — Login/logout berbasis sesi server-side.** Autentikasi terjadi di InsForge (`POST /api/auth/sessions`). Access token (JWT) + refresh token disimpan dalam sesi Laravel terenkripsi (`SESSION_DRIVER=file`) dan di-refresh otomatis oleh middleware saat mendekati kedaluwarsa. Profil user diambil dari tabel `users` proyek (role, is_active).
|
||
- **F2 — Role-based access.** Dua peran: `admin`, `operator` — kolom enum `users.role`. Diimplementasi via policy + Gate Laravel (`Gate::before` untuk admin). Satu panel Filament `/admin`; menu dan aksi difilter per ability; akses langsung ditolak 403.
|
||
- Kriteria terima:
|
||
- User belum login dialihkan ke halaman login.
|
||
- Operator yang mengakses fitur admin mendapat 403.
|
||
|
||
### 5.2 Produk
|
||
|
||
- **F3 — Master produk (CRUD, admin).** Atribut sesuai skema live: `code`, `name`, `unit`, `weight`, `stock_minimum`, `cost_price`, `is_active`.
|
||
- Harga jual **tidak disimpan di master produk**; harga transaksi ditetapkan per baris item pada `order_items.unit_price`.
|
||
- Kriteria terima: produk tampil di daftar, dapat dibuat/diedit; produk dipakai di order tetap konsisten (harga order tersimpan pada item).
|
||
|
||
### 5.3 Stok
|
||
|
||
- **F4 — Stok per produk.** Melihat kuantitas tersedia per produk (`stocks.quantity`, denormalisasi terkontrol).
|
||
- **F5 — Penyesuaian/restok stok (admin).** Mencatat penambahan (restok) atau koreksi; setiap perubahan menghasilkan baris mutasi dan update saldo dalam satu operasi.
|
||
- **F6 — Riwayat mutasi stok (`stock_movements`).** Kolom: `product_id`, `type` (mis. `restock`, `adjustment`, `order_out`), `quantity`, `reference_type`/`reference_id`, `notes`, `created_by`, waktu.
|
||
- Kriteria terima: saldo stok selalu = hasil agregat mutasi (rekonsiliasi); riwayat dapat difilter/dilihat per produk.
|
||
|
||
### 5.4 Pelanggan
|
||
|
||
- **F7 — CRUD pelanggan.** Dapat dilakukan operator dan admin. Kolom live: `customer_code` (`CUST-XXXX`), nama, alamat, telepon, catatan.
|
||
- Kriteria terima: pelanggan dapat dipilih saat membuat order; penghapusan diblokir jika masih direferensikan order.
|
||
|
||
### 5.5 Pesanan (Sales Order)
|
||
|
||
- **F8 — CRUD order + item order.** Satu order memiliki ≥1 item (`order_items`: produk, qty, `unit_price`, `subtotal`). Nomor order otomatis `ORD-YYYYMMDD-XXXX` (sequence harian, immutable).
|
||
- **F9 — Alur status (`orders.order_status`):** `pending → processing → delivering → completed`, alternatif `cancelled` **hanya dari** `pending`/`processing` oleh admin atau operator pembuat (`created_by`). Transisi forward-only, tidak boleh mundur; `completed` bersifat immutable.
|
||
- **F10 — Pengurangan stok otomatis & idempotent.** Saat order menjadi `completed`: fungsi RPC di InsForge menyisipkan mutasi `order_out` (partial unique index `stock_movements(reference_type, reference_id)` sebagai gerbang idempotensi — duplikat dilewati) lalu decrement `stocks.quantity`, secara atomik.
|
||
- **F11 — Cetak surat jalan (PDF)** via `barryvdh/laravel-dompdf`, pratinjau/stream di `/orders/{id}/delivery-note`, nomor berformat `SJL-YYYY/MM/DD-<order_number>`; aksi aktif saat status `delivering`/`completed`.
|
||
- **F12 — Hapus order (soft-delete `deleted_at`)** hanya oleh admin.
|
||
- **F13 — Riwayat status (`order_status_histories`).** Setiap perubahan status tercatat dengan `old_status`, `new_status`, `changed_by`, waktu, catatan.
|
||
- **Data pendukung (skema live):** `order_date`, `delivery_date`, `driver_id`, `payment_method` (mis. `cash`), `payment_status` (mis. `paid`/`unpaid`), `survey_status` (survei kepuasan pasca-penerimaan barang; diisi setelah order `completed`), `notes`, `completed_at`.
|
||
- Validasi dua gerbang oversell: qty divalidasi terhadap saldo saat create/update order **dan** saat transisi ke `completed` (stok kurang → transisi ditolak dengan pesan jelas).
|
||
- Kriteria terima:
|
||
- Order tidak bisa completed jika transisi tidak valid.
|
||
- Proses ulang "completed" pada order yang sama tidak mengurangi stok dua kali.
|
||
- Driver/pengiriman dapat ditetapkan pada order (integrasi modul Drivers).
|
||
|
||
### 5.6 Pengemudi (Drivers)
|
||
|
||
- **F14 — Master driver (admin).** Kolom live: `driver_code` (`DRV-XXXX`), nama, telepon, `is_active`, catatan.
|
||
|
||
### 5.7 Keluhan (Complaints)
|
||
|
||
- **F15 — Catat komplain pelanggan**, terhubung ke order/pelanggan. Nomor `CMP-YYYYMMDD-XXXX`. Status: `open` / `resolved`; kolom pendukung live: `category`, `priority`, `resolution`, `resolved_at`.
|
||
- **F16 — Validasi kepemilikan:** operator hanya boleh menutup keluhan miliknya (`created_by`); admin bebas. Penutupan mengisi `resolution` + `resolved_at`.
|
||
- Kriteria terima: transisi status keluhan tervalidasi; penutupan oleh pihak salah ditolak 403.
|
||
|
||
### 5.8 Dashboard & Laporan
|
||
|
||
- **F17 — Dashboard statistik ringkas** untuk user login — empat widget: jumlah order per status, tabel stok kritis (`quantity <= stock_minimum`), komplain `open` berjalan, daftar pesanan terbaru.
|
||
- **F18 — Laporan (khusus admin):** (1) ringkasan penjualan — filter periode, total & per produk; (2) posisi stok + riwayat mutasi. Ekspor CSV/laba masuk backlog.
|
||
|
||
### 5.9 Manajemen User
|
||
|
||
- **F19 — Kelola user** (khusus admin): daftar, tambah (via admin API InsForge), ubah role/nonaktifkan (`is_active`).
|
||
|
||
## 6. Model Data
|
||
|
||
Sumber kebenaran skema = proyek InsForge live (dikelola via migrasi SQL InsForge, bukan migrasi Laravel). PK UUID di semua tabel; `*_by` mereferensi `auth.users(id)`; waktu disimpan UTC, ditampilkan Asia/Jakarta.
|
||
|
||
| Tabel | Kolom utama (skema live) |
|
||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||
| `users` | `id`, `name`, `email`, `password` (hash mirror), **`role`** (`admin`/`operator`), `is_active` |
|
||
| `customers` | `id`, `customer_code`, `name`, `address`, `phone`, `notes` |
|
||
| `products` | `id`, `code`, `name`, `unit`, `weight`, `stock_minimum`, `cost_price`, `is_active` |
|
||
| `stocks` | `id`, `product_id`, `quantity` (saldo denormalized), `updated_at` |
|
||
| `stock_movements` | `id`, `product_id`, `type`, `quantity`, `reference_type`, `reference_id`, `notes`, `created_by` — partial unique index `(reference_type, reference_id)` |
|
||
| `orders` | `id`, `order_number`, `order_date`, `delivery_date`, `delivery_note_number`, `customer_id`, `driver_id`, `payment_method`, `payment_status`, **`order_status`**, **`survey_status`**, `notes`, `completed_at`, `created_by`, `deleted_at` |
|
||
| `order_items` | `id`, `order_id`, `product_id`, `quantity`, `unit_price`, `subtotal` |
|
||
| `order_status_histories` | `id`, `order_id`, `old_status`, `new_status`, `changed_by`, `notes` |
|
||
| `complaints` | `id`, `complaint_number`, `customer_id`, `order_id`, `complaint_date`, `category`, `description`, `status`, `priority`, `resolution`, `resolved_at`, `created_by` |
|
||
| `drivers` | `id`, `driver_code`, `name`, `phone`, `is_active`, `notes` |
|
||
| `activity_logs` | `id`, `user_id`, `action`, `entity_type`, `entity_id`, `description` |
|
||
|
||
Row Level Security (RLS) tetap aktif di sisi InsForge sebagai lapisan pertahanan kedua; seluruh panggilan data plane membawa JWT user (lihat §8).
|
||
|
||
## 7. Alur Utama
|
||
|
||
### 7.1 Siklus order (happy path)
|
||
|
||
1. Operator membuat order (pilih pelanggan, tambah item produk+qty+harga; sistem validasi saldo stok).
|
||
2. `pending` → diproses (`processing`) → siap kirim (`delivering`, tetapkan driver) → `completed` (RPC idempotent: mutasi `order_out` + decrement stok atomik).
|
||
3. Operator/admin cetak surat jalan PDF (`SJL-…`) untuk diserahkan pengemudi.
|
||
4. Setelah barang diterima: `survey_status` (survei kepuasan) dapat dicatat.
|
||
5. Bila batal: `cancelled` (hanya dari `pending`/`processing`) tanpa efek stok.
|
||
|
||
### 7.2 Penanganan komplain
|
||
|
||
1. Operator mencatat komplain pelanggan (status `open`, nomor `CMP-…`).
|
||
2. Pemilik komplain atau admin menindaklanjuti, mengisi `resolution`, lalu menutup (`resolved`, `resolved_at` terisi).
|
||
|
||
## 8. Kebutuhan Non-Fungsional
|
||
|
||
| Kategori | Persyaratan |
|
||
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| Arsitektur | Laravel 12 + Filament 5 (server-rendered, panel tunggal); InsForge cloud sebagai satu-satunya backend data & auth via REST (`/api/database/records/*`, RPC); tanpa database lokal |
|
||
| Identitas akses | Data plane memakai **JWT user hasil login** (RLS InsForge aktif sebagai lapis kedua); API key admin (`ik_…`) hanya untuk provisioning (manajemen user, seed) |
|
||
| Keamanan | Sesi server-side (`SESSION_DRIVER=file`, terenkripsi); otorisasi policy/Gate; kredensial hanya di `.env` (tidak dikomit) |
|
||
| Konsistensi data | Idempotensi stok wajib (unique index gerbang mutasi via RPC); validasi transisi status di server |
|
||
| Performa | List terpaginasi server-side (offset/limit); query REST selalu menyebut kolom (`select=…`) dan dibatasi `limit` |
|
||
| Zona waktu | Simpan UTC, tampilkan Asia/Jakarta |
|
||
| Auditability | `order_status_histories`, `stock_movements`, `activity_logs` (ditulis service `ActivityLogger`: action snake-case EN, deskripsi ID) |
|
||
| Testability | PHPUnit (±23 tes) dengan `InsForgeFake` untuk HTTP fake; suite Authentication, Authorization, OrderFlow (termasuk idempotensi stok), ComplaintFlow |
|
||
|
||
## 9. Teknologi
|
||
|
||
- **Backend:** PHP ^8.2, Laravel 12, Filament 5, laravel-dompdf (surat jalan PDF).
|
||
- **Frontend:** Filament resources/pages (Blade + Livewire bawaan), Tailwind CSS.
|
||
- **Backend-as-a-Service:** InsForge (Postgres + PostgREST-style REST, auth JWT, RLS) — client wrapper `app/Services/InsForge/InsForgeClient.php` di atas HTTP client Laravel.
|
||
- **Testing:** PHPUnit + `tests/Support/InsForgeFake.php`.
|
||
- **Dibuang dari v1.0:** Inertia, Vue, TypeScript, Sanctum, Ziggy (tidak dipakai; UI sepenuhnya Filament).
|
||
|
||
## 10. Batasan (Out of Scope)
|
||
|
||
- Portal/e-commerce pelanggan mandiri (pelanggan tidak login).
|
||
- Pembayaran online / invoice faktur pajak (kolom pembayaran hanyalah pencatatan manual status/method).
|
||
- Multi-gudang / multi-lokasi stok.
|
||
- Notifikasi WhatsApp/email otomatis.
|
||
- Aplikasi mobile native untuk driver.
|
||
|
||
## 11. Rencana Lanjutan (Backlog)
|
||
|
||
- Notifikasi status order ke pelanggan.
|
||
- Laporan laba (`cost_price` vs `unit_price`) dan ekspor Excel/CSV.
|
||
- Alert otomatis (notifikasi) minimum-stock di dashboard — saat ini baru widget daftar stok kritis.
|
||
- Restore UI untuk order soft-deleted.
|
||
- Chart tren penjualan di dashboard.
|
||
|
||
## 12. Akun Demo (seed)
|
||
|
||
Seed historis `20260818032150_seed-initial-data.sql` di proyek InsForge:
|
||
|
||
| Role | Email | Catatan |
|
||
| -------- | ---------------------- | -------------------------------------------------------------- |
|
||
| Admin | admin@beraspro.test | Password live **berbeda** dari dokumen v1.0 (terverifikasi 401) |
|
||
| Operator | operator@beraspro.test | Password live tidak diketahui dokumen ini |
|
||
|
||
Akun integrasi khusus pengembangan (dibuat via admin API, fase 0):
|
||
|
||
| Role | Email | Password |
|
||
| ----- | ------------------- | ------------------------------------------- |
|
||
| Admin | dev@beraspro.test | acak, dilaporkan sekali, disimpan di `.env` |
|
||
|
||
> Jangan komit password ke repository; simpan hanya di `.env` lokal.
|
||
|
||
## 13. Keputusan Arsitektur (hasil grilling 2026-08-25)
|
||
|
||
1. Backend tunggal InsForge cloud live; tidak ada DB lokal dan tanpa koneksi Postgres langsung (port tidak diekspos).
|
||
2. UI sepenuhnya Filament (panel `/admin`, label bahasa Indonesia); Inertia/Vue/Sanctum/Ziggy dihapus dari rencana.
|
||
3. Skema live adalah sumber kebenaran — aplikasi menyesuaikan konvensi live: nomor `ORD-/SJL-/CMP-`, kolom `order_status`/`survey_status`/pembayaran, soft-delete `deleted_at`, harga jual per item.
|
||
4. Panggilan data membawa JWT user (RLS hidup); refresh token disimpan di sesi server dan di-refresh otomatis; API key admin hanya untuk provisioning.
|
||
5. Alur order forward-only; `cancelled` hanya dari `pending`/`processing` oleh admin/pembuat; `completed` immutable; pengurangan stok lewat RPC idempotent saat `completed`.
|
||
6. Uang integer rupiah tanpa desimal; UUID untuk semua PK; audit via `ActivityLogger`.
|
||
7. Implementasi bertahap 0–6 (fondasi → auth → master → stok → order+PDF → komplain → dashboard/laporan/user), tiap fase diakhiri suite hijau.
|