You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
11 KiB
Markdown
172 lines
11 KiB
Markdown
@AGENTS.md
|
|
|
|
# Cleo Minimarket
|
|
|
|
Aplikasi Next.js 16 (App Router) untuk menggantikan aplikasi back-office minimarket lama berbasis
|
|
PHP di `old-source/`. Kode baru ditulis ulang dari nol; `old-source/` hanya referensi.
|
|
|
|
## Stack
|
|
|
|
- Next.js 16.4 + React 19, TypeScript strict, Tailwind CSS 4, pnpm 11.
|
|
- Drizzle ORM + `pg` untuk database aplikasi (Postgres).
|
|
- Better Auth 1.7 untuk login: Drizzle adapter ke Postgres, plugin `username`, sign-up publik
|
|
ditutup (`disableSignUp`). CLI-nya package `auth` (bukan `@better-auth/cli` yang berhenti di 1.4).
|
|
- shadcn/ui 4 preset `base-nova` (di atas Base UI, bukan Radix). `cn()` dari package `cn`
|
|
milik shadcn, pengganti `clsx` + `tailwind-merge`.
|
|
|
|
## Dua database
|
|
|
|
| Env var | Database | Isi |
|
|
| -------------------- | -------------------------------- | ----------------------------------------- |
|
|
| `DATABASE_URL` | Postgres (Docker lokal) | Milik aplikasi ini: user, session, auth. |
|
|
| `AMIGO_DATABASE_URL` | MariaDB 10.4 `cleo` | Data bisnis lama: barang, transaksi, dll. |
|
|
|
|
- Development memakai **salinan** `cleo` di Docker lokal (`localhost:3307`, user `cleo_next`),
|
|
hasil `mysqldump` dari server LAN `192.168.3.3`. Server LAN adalah produksi yang dipakai toko;
|
|
development tidak pernah menulis ke sana. Restore salinan lokal = dump + import ulang.
|
|
|
|
- Postgres dikelola penuh oleh Drizzle migration (`db:generate` lalu `db:migrate`).
|
|
- MariaDB `cleo` masih dipakai aplikasi PHP lama. **Jangan pernah** `generate`, `migrate`, atau
|
|
`push` ke database ini. Schema Drizzle-nya hanya boleh berasal dari `pnpm db:amigo:pull`
|
|
(config `drizzle-amigo.config.ts`), jangan diedit manual. Script itu menjalankan
|
|
`scripts/fix-amigo-schema.ts` yang memperbaiki bug introspeksi drizzle-kit di MariaDB
|
|
(default ber-kutip, primary key hilang, import kurang). Koneksi: `amigoDb` dari `@/db/amigo`.
|
|
- Kolom tanggal berisi `0000-00-00` (tanggal nol, diizinkan `sql_mode` server). Schema memakai
|
|
`date({ mode: 'string' })`, jadi nilai itu sampai sebagai string; anggap sebagai "kosong",
|
|
jangan diubah ke `Date` (hasilnya `Invalid Date`).
|
|
- Aplikasi ini melakukan CRUD ke MariaDB (data master, barang, transaksi), jadi user DB-nya
|
|
butuh `SELECT`, `INSERT`, `UPDATE`, `DELETE`, tapi tanpa hak DDL (`CREATE`, `ALTER`, `DROP`).
|
|
- UI tidak meniru aplikasi lama; fitur dan aturan bisnisnya yang ditiru, tampilannya dirombak.
|
|
- Selama transisi, aplikasi PHP dan aplikasi ini menulis ke MariaDB bersamaan. Penomoran
|
|
otomatis (mis. kode pelanggan) dan update stok harus aman dari bentrok.
|
|
- Hampir semua tabel bisnis `cleo` ber-engine **MyISAM**: tidak ada transaksi (rollback tidak
|
|
membatalkan apa pun) dan `SELECT ... FOR UPDATE` tidak mengunci baris. Untuk bentrok pakai
|
|
update atomik satu statement (`stok = stok - ?`) atau `LOCK TABLES`; jangan baca-hitung-tulis.
|
|
Penulisan master + detail (mis. `m_nota_tunai` + `d_nota_tunai`) bisa tersimpan setengah kalau
|
|
gagal di tengah, jadi validasi semua input dulu sebelum statement pertama. Mengubah engine ke
|
|
InnoDB adalah `ALTER`, dilarang selama PHP masih jalan.
|
|
- Database `cleo` juga berisi tabel dari proyek lain (Laravel: `users`, `sessions`, `jobs`,
|
|
`cache`, `migrations`, dll.; Better Auth: `user`, `account`, `session`, `verification`;
|
|
Prisma: `_prisma_migrations`). Bukan milik aplikasi ini dan dianggap masih dipakai aplikasi
|
|
lain; jangan disentuh dan kecualikan dari `drizzle-kit pull`. User DB produksi diberi hak per
|
|
tabel bisnis, bukan `cleo.*`, supaya tabel-tabel itu tidak terjangkau.
|
|
- Server MariaDB 10.4, bukan MySQL 8: hindari fitur yang tidak didukung (mis. sebagian fungsi
|
|
JSON dan CTE tertentu). Driver: `mysql2`.
|
|
|
|
## Struktur folder
|
|
|
|
Tanpa folder `src/`. Kode aplikasi di root, sejajar config.
|
|
|
|
```
|
|
app/
|
|
(auth)/actions.ts Server Actions auth (login, logout), dipakai lintas route group
|
|
(auth)/login/page.tsx halaman publik (belum login)
|
|
(dashboard)/layout.tsx kerangka UI halaman yang wajib login (bukan tempat cek session)
|
|
(dashboard)/<fitur>/
|
|
page.tsx Server Component: ambil data lewat data/, render
|
|
actions.ts Server Actions fitur ini ('use server')
|
|
_components/ komponen khusus fitur ini
|
|
api/auth/[...all]/route.ts handler Better Auth (satu-satunya route handler yang wajar)
|
|
components/
|
|
ui/ komponen shadcn (generated, jangan diedit manual kecuali perlu)
|
|
<nama>.tsx komponen yang dipakai lintas fitur
|
|
cari-master.tsx kotak pencarian master (prop basePath + placeholder)
|
|
pagination.tsx tombol halaman daftar (prop page, hasNext, hrefPage)
|
|
data/ Data Access Layer: fungsi baca/tulis DB, selalu 'server-only'
|
|
db/
|
|
index.ts, schema.ts koneksi + schema Postgres
|
|
amigo/index.ts, amigo/schema.ts koneksi + schema MariaDB (hasil `drizzle-kit pull`)
|
|
lib/
|
|
auth.ts config Better Auth (server)
|
|
auth-client.ts client Better Auth
|
|
pagination.ts PER_PAGE, parse `?page=`, dan href halaman
|
|
utils.ts helper umum: cn (dibuat shadcn) dan escapeLike untuk LIKE
|
|
drizzle/ file migrasi Postgres (generated, ikut di-commit)
|
|
proxy.ts pengganti middleware di Next 16; hanya cek ringan (redirect)
|
|
```
|
|
|
|
- Nama folder route dan file: kebab-case. Komponen React: PascalCase untuk nama fungsinya.
|
|
- Folder berawalan `_` tidak menjadi route; pakai untuk komponen yang dikolokasikan.
|
|
- Route group `(nama)` hanya untuk mengelompokkan layout, tidak muncul di URL.
|
|
- Import pakai alias `@/` (mis. `@/db`, `@/data/barang`).
|
|
- `actions.ts` dikolokasikan dengan fitur yang memakainya. Action yang dipakai lintas route group
|
|
(mis. `logout` dipanggil dari `(dashboard)`) ditaruh di level route group pemiliknya, lalu
|
|
di-import lewat alias (`@/app/(auth)/actions`), bukan path relatif `../(auth)/...`.
|
|
|
|
## Aturan data dan Server Actions
|
|
|
|
- **Mutasi (create/update/delete, submit form) wajib lewat Server Action.** Tidak membuat
|
|
route handler `app/api/*` untuk hal yang bisa dikerjakan Server Action.
|
|
- **Membaca data tidak lewat Server Action.** Server Action adalah POST dan dijalankan berurutan,
|
|
jadi tidak cocok untuk fetch. Data dibaca di Server Component dengan memanggil fungsi di `data/`.
|
|
- Setiap file `actions.ts` diawali `'use server'`. Setiap action:
|
|
1. Cek session dan hak akses di dalam action itu sendiri. Menyembunyikan tombol di UI bukan
|
|
pengaman, karena action bisa dipanggil langsung lewat POST.
|
|
2. Validasi input (`FormData`/argumen) dengan zod sebelum menyentuh DB.
|
|
3. Panggil fungsi di `data/`, bukan query langsung dari action.
|
|
4. Kembalikan objek kecil yang dibutuhkan UI (mis. `{ ok: true }` atau
|
|
`{ ok: false, error: string }`), bukan baris DB mentah.
|
|
5. Segarkan cache dengan `revalidatePath`/`updateTag` sebelum `redirect`, karena `redirect`
|
|
melempar exception dan kode sesudahnya tidak jalan.
|
|
6. Error yang tidak terduga di-log di server dengan konteks yang cukup untuk dilacak: nama
|
|
action, user id, input yang relevan (tanpa password/secret), dan error aslinya (stack ikut).
|
|
Ke UI hanya dikirim pesan bahasa Indonesia yang jelas dan bisa ditindaklanjuti user
|
|
(mis. "Kode pelanggan sudah dipakai, coba simpan ulang"), tidak pernah pesan error DB mentah.
|
|
- Form di client memakai `useActionState` untuk menampilkan error/pending dari action.
|
|
- File di `data/` diawali `import "server-only"` supaya tidak bisa ter-import ke client
|
|
component. `db/` dan `lib/auth.ts` sengaja tanpa itu karena dibaca CLI `auth` di luar Next.js
|
|
(package `server-only` tidak di-install; Next.js menanganinya sendiri). Hanya `data/`, `db/`,
|
|
dan `lib/auth.ts` yang membaca `process.env` untuk secret.
|
|
- Session dicek lewat `getCurrentUser()` di `data/auth.ts` (redirect ke `/login` kalau tidak ada),
|
|
dipanggil oleh page dan action yang butuh user. Layout tidak dipakai untuk cek session: layout
|
|
tidak di-render ulang saat navigasi antar halaman di dalamnya.
|
|
- `cacheComponents` aktif: komponen yang membaca session (`headers()`/`cookies()`) wajib berada
|
|
di dalam `<Suspense>`, kalau tidak build gagal. Jangan `await` session di level atas layout.
|
|
- `proxy.ts` hanya untuk redirect cepat berdasarkan ada/tidaknya cookie session, bukan pengganti
|
|
`getCurrentUser()`.
|
|
- Query selalu lewat Drizzle query builder atau `sql` template tag (parameterized). Jangan
|
|
menyusun SQL dengan string concatenation.
|
|
|
|
## Kode lama (`old-source/`)
|
|
|
|
Read-only, jangan diedit dan jangan di-import. Dipakai untuk memahami fitur dan aturan bisnis.
|
|
|
|
- PHP prosedural + jQuery, akses DB lewat `mysqli`/`mysql_*` langsung di tiap halaman.
|
|
Banyak query disusun dengan string concatenation (rentan SQL injection); jangan tiru polanya.
|
|
- Peta fitur ada di `old-source/menu.php`: Data Master (pelanggan, karyawan, group/sub group
|
|
product, rak, supplier, satuan), Barang, Pengadaan, Barcode, Retur Pembelian, Transfer Barang,
|
|
Revisi, Laporan Persediaan/Beli/Jual, BON, Pembayaran.
|
|
- Helper bisnis di `old-source/func_lib.php`. Hal yang tidak terlihat dari namanya:
|
|
- Login lama mencocokkan `karyawan.username` dan `karyawan.password` (hash MD5), lalu
|
|
mengisi session `useracc`, `nik`, `job` (`id_pekerjaan`), `namatoko`.
|
|
- Jabatan dibaca dari `karyawan.id_pekerjaan` berupa teks: `Pemimpin Toko`,
|
|
`Asisten Pemimpin Toko`, `Petugas Administrasi`.
|
|
- Harga pokok ditampilkan dalam kode huruf: `convHrgP`/`DeconvHrgP` memetakan angka 1-0 ke
|
|
`B R A W N F O X E S`; profit dipetakan `convProfit` ke `D J U N G K A R E T`.
|
|
- Format tanggal tampilan `dd-mm-yyyy` (`encTgl`), DB `yyyy-mm-dd` (`decTgl`).
|
|
- `bukaKoneksiPusat()` merujuk konstanta `*_PUSAT` yang tidak didefinisikan di
|
|
`db_login.inc.php`; fitur koneksi ke DB pusat kemungkinan sudah mati.
|
|
- File `*~` dan `(copy)` adalah sisa backup editor, abaikan.
|
|
|
|
## Perintah
|
|
|
|
```
|
|
pnpm dev jalankan dev server
|
|
pnpm build build production
|
|
pnpm lint eslint
|
|
pnpm exec tsc --noEmit type-check
|
|
pnpm db:generate buat migrasi dari db/schema.ts (Postgres)
|
|
pnpm db:migrate jalankan migrasi ke Postgres
|
|
pnpm db:studio lihat isi Postgres di https://local.drizzle.studio
|
|
pnpm db:seed buat akun admin dari SEED_ADMIN_* di .env (idempotent)
|
|
pnpm db:amigo:pull introspeksi MariaDB ke db/amigo/schema.ts (satu-satunya perintah drizzle-kit untuk MariaDB)
|
|
docker compose up -d nyalakan Postgres + MariaDB lokal (docker-compose.yml)
|
|
```
|
|
|
|
Postgres lokal: service di `docker-compose.yml`, container `cleo-postgres` (image `postgres:17`,
|
|
port 5432, volume `cleo-pgdata`). MariaDB lokal: container `cleo-mariadb` (image `mariadb:10.4`,
|
|
port 3307, volume `cleo-mariadb-data`). Setup laptop baru ada di `README.md`.
|
|
|
|
Query ke MariaDB dari PowerShell 5.1: jangan pakai `mysql -e "..."` (kutip ganda dibuang
|
|
PowerShell); kirim lewat pipe: `'SELECT 1' | docker exec -i cleo-mariadb sh -c 'mysql -uroot -p$MARIADB_ROOT_PASSWORD -N cleo'`.
|