Alamat: Jalan Gamers No. 8, Semarang | Telp: (024) 1234567
Sistem informasi manajemen apotek berbasis web fullstack untuk mengelola stok obat, pembelian dari PBF/supplier, penjualan kasir (HV & Resep), laporan harian, dan closing kasir per shift.
| Layer | Teknologi |
|---|---|
| Frontend | React 18 + Vite + TailwindCSS + React Router v6 |
| Backend | Node.js + Express.js |
| Database | MySQL 5.7+ / MariaDB 10.3+ |
| Auth | JWT (JSON Web Token, expire 12h) |
| HTTP Client | Axios (dengan interceptor token otomatis) |
| Icons | Lucide React |
| Notifikasi | React Hot Toast |
| Tanggal | date-fns |
Target deployment:
- Backend β Shared Hosting via cPanel Node.js Selector, atau Railway/Render
- Frontend β Vercel (static build
dist/) atau subdomain shared hosting
apotek-moro-mari/
βββ backend/
β βββ index.js # Entry point server Express
β βββ .env.example # Template konfigurasi environment
β βββ config/
β β βββ database.js # MySQL connection pool (promise-based)
β β βββ schema.sql # DDL lengkap + seed data awal
β βββ middleware/
β β βββ auth.js # JWT verify middleware + role guard
β βββ routes/
β βββ auth.js # POST /login, POST /change-password
β βββ barang.js # CRUD obat, harga otomatis, stock opname
β βββ pembelian.js # Pembelian, supplier, histori, retur
β βββ penjualan.js # Kasir, laporan, closing, kas apotek
β
βββ frontend/
βββ index.html
βββ vite.config.js
βββ tailwind.config.js
βββ postcss.config.js
βββ src/
βββ main.jsx
βββ App.jsx # Router utama + ProtectedRoute
βββ context/
β βββ AuthContext.jsx # Global auth state, login/logout action
βββ utils/
β βββ api.js # Axios instance + semua fungsi API call
βββ hooks/
β βββ useAuth.js # Hook untuk konsumsi AuthContext
βββ components/
β βββ Layout.jsx # Sidebar + header wrapper + <Outlet>
β βββ Sidebar.jsx # Navigasi sidebar dengan active state
β βββ ProtectedRoute.jsx # Guard route berdasar login + role
β βββ ui/
β βββ Button.jsx
β βββ Modal.jsx
β βββ Table.jsx
β βββ Badge.jsx # Badge warna grup obat
β βββ Input.jsx
β βββ Select.jsx
βββ pages/
βββ Login.jsx
βββ Dashboard.jsx # Summary + peringatan stok kritis
βββ stock/
β βββ DaftarBarang.jsx
β βββ SettingHarga.jsx
β βββ StockOpname.jsx
βββ pembelian/
β βββ DaftarSupplier.jsx
β βββ InputPembelian.jsx
β βββ HistoriPembelian.jsx
β βββ ReturPembelian.jsx
βββ penjualan/
β βββ KasirHV.jsx
β βββ KasirResep.jsx
β βββ LaporanPenjualan.jsx
β βββ ClosingKasir.jsx
βββ laporan/
βββ KasApotek.jsx
| Tabel | Fungsi |
|---|---|
users |
Akun login dengan role RBAC |
barang |
Master data obat/produk apotek |
kelas_terapi |
Kategori terapi obat |
supplier |
Data PBF / distributor |
pelanggan |
Data member / pasien |
pembelian |
Header faktur pembelian dari PBF |
pembelian_detail |
Item per baris faktur pembelian |
retur_pembelian |
Header retur barang ke PBF |
retur_pembelian_detail |
Item retur |
penjualan |
Header transaksi kasir (HV/Resep) |
penjualan_detail |
Item per transaksi penjualan |
stock_opname |
Rekam fisik vs sistem per periode |
kas_apotek |
Jurnal kas harian (debit/kredit) |
closing_kasir |
Rekap pendapatan per shift |
supplier ββ< pembelian ββ< pembelian_detail >ββ barang
supplier ββ< retur_pembelian ββ< retur_pembelian_detail >ββ barang
pelanggan ββ< penjualan ββ< penjualan_detail >ββ barang
users ββ< penjualan
users ββ< pembelian
barang ββ< stock_opname
users.role β 'apoteker' | 'apoteker_pendamping' | 'admin' | 'asisten_apoteker'
barang.grup β 'hijau' -- obat bebas, bebas terbatas
β 'merah' -- obat keras, narkotika, psikotropika
β 'biru' -- barang konsinyasi
penjualan.tipe β 'hv' | 'resep'
penjualan.shift β 'pagi' | 'siang'
penjualan.status β 'draft' | 'confirmed' | 'void'
kas_apotek.jenis β 'debit' | 'kredit'Dihitung di backend setiap kali barang dibuat/diupdate:
harga_beli β HNA (Harga Netto Apotek, sebelum PPN)
Γ 1.10
harga_jual β HNA + PPN 10%
Γ 1.10
harga_hv β harga_jual + margin 10% (untuk penjualan bebas/HV)
Γ 1.08
harga_resep β harga_hv + margin 8% (untuk penjualan dengan resep dokter)
Frontend menampilkan preview kalkulasi secara real-time saat input harga_beli di form barang.
| Role | Stok | Pembelian | Kasir | Laporan | Master Data |
|---|---|---|---|---|---|
apoteker |
β Full | β Full | β Full | β Full | β Full |
apoteker_pendamping |
β Full | β Full | β Full | β Full | β |
admin |
π View | β Full | β Full | β Full | β |
asisten_apoteker |
π View | β | β Kasir saja | π View | β |
Guard diimplementasi di:
- Backend: middleware
roleMiddleware(...roles)pada route tertentu - Frontend: komponen
ProtectedRoute+ hide/disable tombol berdasarkanuser.role
Kasir : A{DDMMYY}{3-digit-seq} contoh: A140420065
Resep (via modal) menggunakan nota yang sama (tipe tetap 'hv')
Retur PBF : RT{YYYYMMDD}{3-digit-seq} contoh: RT202004140001
Nomor nota di-generate otomatis oleh backend. Urutan (seq) dihitung dari jumlah transaksi di tanggal dan tipe yang sama + 1.
Semua endpoint (kecuali /api/auth/login dan /api/health) memerlukan header:
Authorization: Bearer <jwt_token>
POST /api/auth/login
Body: { username, password }
Response: { token, user: { id, username, nama, role } }
POST /api/auth/change-password [auth]
Body: { old_password, new_password }
GET /api/barang ?search=&grup=hijau|merah|biru&low_stock=true
GET /api/barang/:id
POST /api/barang
Body: { kode, nama_barang, satuan, pabrik, grup, kelas_terapi_id,
stock_minimum, harga_beli }
PUT /api/barang/:id
Body: (sama seperti POST, minus kode)
DELETE /api/barang/:id β soft delete (is_active = 0)
POST /api/barang/stock-opname/submit
Body: { periode, tanggal, items: [{ barang_id, stock_sistem, stock_fisik, keterangan }] }
GET /api/pembelian ?from=YYYY-MM-DD&to=YYYY-MM-DD&supplier_id=
GET /api/pembelian/:id β include detail items
POST /api/pembelian
Body: { no_faktur, tanggal, supplier_id,
items: [{ barang_id, jumlah, harga_hna, diskon_persen, diskon_nominal,
harga_netto, jumlah_harga }] }
GET /api/pembelian/histori/barang/:barang_id ?from=&to=
GET /api/pembelian/supplier/list
POST /api/pembelian/supplier/create
Body: { kode, nama_pbf, alamat, kota, no_telp, jatuh_tempo }
PUT /api/pembelian/supplier/:id
POST /api/pembelian/retur/create
Body: { no_retur, tanggal, supplier_id, keterangan,
items: [{ barang_id, jumlah, harga_satuan, jumlah_harga }] }
GET /api/pembelian/retur/list
GET /api/penjualan ?from=&to=&shift=pagi|siang&tipe=hv|resep
GET /api/penjualan/:id β include detail items
POST /api/penjualan
Body: { tanggal, shift, tipe, pelanggan_id (opsional),
items: [{ barang_id, jumlah, harga_satuan, subtotal }],
tunai, non_tunai }
Response: { no_nota, id, kembalian }
POST /api/penjualan/:id/void β kembalikan stok
GET /api/penjualan/laporan/per-barang ?from=&to=&barang_id=&shift=
GET /api/penjualan/closing/summary ?from=&to=&shift=
GET /api/penjualan/kas/list ?from=&to=
POST /api/penjualan/kas/create
Body: { tanggal, keterangan, jenis, nominal, tanggal_transaksi }
GET /api/dashboard β summary hari ini + stok kritis
GET /api/kelas-terapi
GET /api/pelanggan ?search=
POST /api/pelanggan Body: { kode, nama, no_hp, alamat, tipe }
GET /api/health
PORT=5000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_db_password
DB_NAME=apotek_moro_mari
JWT_SECRET=ganti_dengan_string_acak_panjang
FRONTEND_URL=http://localhost:5173VITE_API_URL=http://localhost:5000/api# 1. Setup database
mysql -u root -p < backend/config/schema.sql
# 2. Backend
cd backend && cp .env.example .env
# edit .env
npm install && npm run dev
# 3. Frontend
cd frontend && cp .env.example .env
# edit .env β VITE_API_URL
npm install && npm run dev| Username | Password | Role |
|---|---|---|
admin |
1234 |
admin |
dyah |
1234 |
apoteker |
β οΈ Wajib ganti password setelah login pertama kali.
- Push repo ke GitHub
- Import project di vercel.com
- Set root directory:
frontend - Set environment variable:
VITE_API_URL=https://your-backend-domain.com/api - Deploy β Vercel otomatis build dengan
npm run build
- Login cPanel β Setup Node.js App
- Buat app baru:
- Node.js version: 18+
- Application root:
backend - Application startup file:
index.js
- Upload folder
backend/ke server via File Manager - Buat file
.envdi folder backend (isi sesuai.env.example) - Import
schema.sqlvia phpMyAdmin - Klik "Run NPM Install" di panel Node.js App
- Start/Restart app
- Push repo ke GitHub
- Connect repo di Railway/Render
- Set root directory:
backend - Set environment variables (DB_HOST, DB_USER, dll)
- Start command:
npm start - Deploy
# Clone repo
git clone <repo-url> && cd kasir-react/backend
# Install
cp .env.example .env && nano .env
npm install
# Run with PM2
npm install -g pm2
pm2 start index.js --name apotek-backend
pm2 save && pm2 startup