1
Ringkasan Proyek Hydro
Hydro adalah website internal untuk mencatat produk, stok per batch, pembelian pelanggan, pembayaran, retur, piutang, dan laporan pada perusahaan air minum kemasan. Repository ini memisahkan tiga bagian: backend/ berisi Django 5.2 LTS dengan Django REST Framework beserta aturan bisnis dan hak akses, frontend/ berisi Next.js statis dengan TypeScript, Tailwind, dan shadcn/ui, sedangkan deploy/ memuat Nginx, skrip rilis, backup, dan pemulihan. README.md:1-111
Dua berkas compose melayani dua keperluan berbeda. compose.yaml mencoba aplikasi di komputer sendiri dengan satu perintah, sementara compose.prod.yaml menjalankan produksi dengan tiga service web, api, dan db. README.md:1-111
Peta modul
Diagram berikut memetakan modul yang benar-benar terdaftar di INSTALLED_APPS beserta ketergantungannya pada database dan lapisan deploy. Modul inti menjadi tempat izin, paginasi, dan skema bersama; akun menyimpan model pengguna kustom akun.Pengguna; produk, stok, pembelian, dan laporan memikul pencatatan harian. backend/config/settings.py:26-452
graph TD
WEB["web: Nginx"]
API["api: gunicorn config.wsgi"]
DB[("PostgreSQL 18.1")]
subgraph FE["frontend"]
KLIEN["src/lib/api.ts"]
HALAMAN["src/app/(app)"]
end
subgraph BE["backend"]
INTI["inti"]
AKUN["akun"]
PRODUK["produk"]
STOK["stok"]
BELI["pembelian"]
LAPOR["laporan"]
end
DEPLOY["deploy: rilis.sh, backup.sh"]
HALAMAN --> KLIEN
KLIEN -->|"fetch + CSRF"| WEB
WEB --> API
API --> INTI
API --> AKUN
API --> PRODUK
API --> STOK
API --> BELI
API --> LAPOR
INTI --> DB
AKUN --> DB
PRODUK --> DB
STOK --> DB
BELI --> DB
LAPOR --> DB
DEPLOY -->|"pull image GHCR"| WEB
DEPLOY -->|"manage.py migrate"| API
style DB fill:#336791,color:#fff
style INTI fill:#f59e0b,color:#000
style WEB fill:#f59e0b,color:#000
style API fill:#f59e0b,color:#000
style DEPLOY fill:#8b5cf6,color:#fff
style BELI fill:#2d7d46,color:#fff
style STOK fill:#2d7d46,color:#fff
%% fig: Peta modul Hydro
%% desc: Halaman frontend memanggil Nginx lalu gunicorn; setiap app Django membaca dan menulis ke PostgreSQL, sedangkan skrip deploy menarik image dan menjalankan migration.| Lapisan | Modul atau berkas | Peran |
|---|---|---|
| Backend | inti | izin, paginasi, format, skema OpenAPI bersama |
| Backend | akun | model Pengguna dan alur 2FA lewat allauth.mfa |
| Backend | produk | Kategori, Produk, SatuanProduk, HargaProduk |
| Backend | stok | BarangMasuk, BatchProduk, MutasiStok, StokOpname |
| Backend | pembelian | Pelanggan, Transaksi, Pembayaran, Retur |
| Backend | laporan | data.py untuk angka laporan dan ekspor.py untuk berkas |
| Frontend | src/lib/api-types.ts | hasil openapi-typescript dari backend/schema.yaml |
| Deploy | deploy/rilis.sh | backup, ganti tag versi, migrate, lalu up -d |
Baris tabel di atas bersandar pada daftar app di setelan dan pada nama berkas nyata di laporan/ serta frontend/src/lib/. backend/config/settings.py:26-452 Tipe TypeScript dibangkitkan ulang oleh skrip tipe-api yang membaca ../backend/schema.yaml dan menulis src/lib/api-types.ts. frontend/package.json:5-113 Skrip rilis.sh sendiri menjalankan bash deploy/backup.sh, memperbarui APP_VERSION di .env, docker compose pull web api, docker compose run --rm api python manage.py migrate, lalu docker compose up -d. deploy/rilis.sh:1-134
Cara menjalankan
| Cara | Berkas | Service | Akses |
|---|---|---|---|
| Coba lokal | compose.yaml | web, api, db | port 8080 pada host |
| Development | compose.dev.yaml | web, api, db | port 3000 untuk Next.js |
| Produksi | compose.prod.yaml | web, api, db | port 443 diikat ke IP Tailscale |
Pada compose.yaml service web membangun image dari deploy/web.Dockerfile, memetakan port 127.0.0.1:8080:8080, dan menunggu api berstatus service_healthy. Service api menjalankan migrate --noinput, isi_demo, lalu gunicorn config.wsgi --bind 0.0.0.0:8000 --workers 2. compose.yaml:3-335 Service db memakai image postgres:18.1 dengan healthcheck pg_isready -U hydro -d hydro setiap 3 detik dan 20 kali retry. compose.yaml:35-466
Di produksi, image diambil dari GHCR dengan tag ${APP_VERSION}, web diikat ke ${TAILSCALE_IP}:443:8443, dan kedua service aplikasi berjalan read_only dengan cap_drop: [ALL]. Jaringan app dan data bersifat internal, sehingga hanya edge yang menghadap luar. compose.prod.yaml:1-637
Setelan Django membaca konfigurasi dari environment. SECRET_KEY diambil dari DJANGO_SECRET_KEY dan sengaja gagal saat start bila variabel itu kosong ketika DEBUG mati. Koneksi database default memakai django.db.backends.postgresql dengan CONN_MAX_AGE 60 detik. backend/config/settings.py:77-898
Aturan pencatatan inti
Stok disimpan dalam botol per batch. BatchProduk menegakkan sisa >= 0 lewat constraint sisa_tidak_minus dan menjaga satu nomor batch unik per produk lewat batch_unik_per_produk. backend/stok/models.py:34-549 Setiap perubahan stok wajib melewati ubah_stok() di backend/stok/layanan.py, yang menolak sisa minus dengan pesan Stok batch {nomor_batch} tidak boleh minus. dan langsung menulis baris MutasiStok. backend/stok/layanan.py:21-4410 Jenis mutasi yang tercatat adalah masuk, penjualan, retur, rusak, kedaluwarsa, opname, dan batal. backend/stok/models.py:57-7911
Saat penjualan, baris batch dikunci lebih dulu lewat kunci_batch(), yang mengurutkan select_for_update() berdasarkan id supaya dua transaksi tidak saling menunggu. backend/stok/layanan.py:47-5012 Nota dibuat oleh buat_transaksi(). Fungsi itu memeriksa kunci_idempoten lebih dahulu, sehingga klik ganda atau kirim ulang mengembalikan nota yang sudah ada, dan seluruh penulisan berjalan di dalam satu transaction.atomic(). backend/pembelian/layanan.py:31-4613
graph LR
BM["catat_barang_masuk"] --> B["BatchProduk"]
B --> U["ubah_stok"]
U --> M["MutasiStok"]
T["buat_transaksi"] --> K["kunci_batch"]
K --> U
T --> N["Transaksi + ItemTransaksi"]
N --> S["sisa_tagihan"]
X["ke_xlsx"] --> F["berkas .xlsx"]
style B fill:#336791,color:#fff
style M fill:#336791,color:#fff
style N fill:#336791,color:#fff
style U fill:#2d7d46,color:#fff
style K fill:#f59e0b,color:#000
style S fill:#8b5cf6,color:#fff
style F fill:#8b5cf6,color:#fff
%% fig: Jalur tulis stok dan nota
%% desc: Barang masuk membentuk batch, perubahan sisa selalu lewat ubah_stok dan tercatat di mutasi_stok, sedangkan transaksi mengunci batch sebelum menulis nota dan sisa tagihan.Piutang tidak dihitung ulang di aplikasi. Kolom sisa_tagihan pada Transaksi adalah GeneratedField dari total - total_retur - terbayar dengan db_persist=True, dan constraint tagihan_tidak_minus menjaga nilainya tidak negatif. backend/pembelian/models.py:34-5714 Harga jual dibaca dari HargaProduk terbaru yang berlaku_mulai tidak melewati tanggal transaksi, lewat manager SatuanQuerySet.dengan_harga. backend/produk/models.py:46-8015
⚠️
compose.yamlmemakai password contoh dan berjalan tanpa HTTPS, jadi jangan dipakai untuk produksi. Hentikan dengandocker compose down; menambahkan-vikut menghapus volume datanya. README.md:13-2316
Ekspor laporan menghasilkan berkas unduhan. ke_xlsx() menulis satu lembar dengan header tebal, mengubah Decimal menjadi float, memberi format #,##0.00 untuk kolom uang, dan memaksa data_type = "s" pada sel teks supaya isian pengguna tidak terbaca sebagai rumus Excel. backend/laporan/ekspor.py:24-4917 Rincian tiap alur ada di Alur Stok, Batch FEFO, dan Opname serta Alur Transaksi, Pembayaran, Retur, dan Piutang.