# Melati ERP & POS — Agent Guide

## Quick start

```bash
composer run setup   # install + .env + key + migrate + npm build
composer run dev     # artisan serve + queue:listen + pail + vite (concurrently)
composer run test    # config:clear + php artisan test
npm run dev          # Vite dev server only
```

## Architecture

- **Laravel 13** / PHP 8.3, **MySQL** on port **3307** (`.env`), SQLite `:memory:` in tests (`phpunit.xml`)
- **Single DB, multi-branch**. 4 branches seeded: `Toko ATK`, `Percetakan`, `Snack`, `E-Toko`
- **4 roles**: `main_owner` (super), `partial_owner` (read-only dashboard per branch), `admin_supply` (CRUD, branch-restricted via `branch_user` pivot), `kasir` (POS only, own branch)
- Session, cache, and queue all use **database driver** (not file/redis)
- `.npmrc` has `ignore-scripts=true`

## Routing & middleware

- Custom `role` middleware alias registered in `bootstrap/app.php`, usage: `role:main_owner,kasir` (comma-separated)
- Route-model binding on `{branch}` → `App\Models\Branch`; all business entities scoped by `branch_id`
- `partial_owner` can only access owner dashboard routes (`/owner/branch/{branch}`)
- `kasir` middleware enforces branch ownership (403 if `user.branch_id != route branch`)

```
/owner/branch/{branch}         # main_owner, partial_owner
/admin/{branch}/{suppliers|products|expenses}  # admin_supply, main_owner
/pos/{branch}                  # kasir, main_owner
```

## Models & DB conventions

- DB column names use **Indonesian**: `nama_barang`, `kode_barang`, `harga_beli`, `harga_jual_standar`, `jenis_pengeluaran`
- `Product.meta_data` is JSON → cast to `array` in model
- `Supplier.branch_id` is nullable (global suppliers)
- `transactions` and `transaction_details` use `unit_price`/`subtotal` (sales) and `unit_cost`/`subtotal_cost` (HPP columns added in migration `2026_05_13_015112`)

## Views

4 layout files, use the correct one for each context:
- `layouts.admin` — sidebar + branch switcher + module tabs (admin_supply/main_owner)
- `layouts.owner` — dark sidebar, executive KPIs, Chart.js
- `layouts.pos` — dark header, branch badge, minimal nav
- `layouts.app` — default Breeze (used for profile pages)

Alpine.js for interactivity (POS cart, product filtering), Chart.js for dashboards, SweetAlert2 for toasts.

## Test

- `phpunit.xml` overrides every connection: SQLite `:memory:`, `QUEUE_CONNECTION=sync`, `CACHE_STORE=array`, `SESSION_DRIVER=array`, `MAIL_MAILER=array`
- Test suites: `Unit` (`tests/Unit`), `Feature` (`tests/Feature`)
- Run: `composer run test` or `vendor/bin/phpunit`

## Seeders

- `BranchSeeder` + `UserSeeder` + `SupplierSeeder` + `ProductSeeder` + `OperationalExpenseSeeder`
- All seed user passwords: **`password`**
- Users: owner@cv.com (main_owner), admin.atk@cv.com (admin_supply, allowed: ATK+Percetakan+E-Toko), admin.snack@cv.com (admin_supply, snack branch), kasir.atk@cv.com (kasir atk branch), kasir.snack@cv.com (kasir snack branch)

## Utilities

- `scratch/backfill_hpp.php` — one-off script to backfill `unit_cost`/`subtotal_cost` on existing transaction_details (run via `php scratch/backfill_hpp.php`)

## What's NOT here

- No CI/CD, Docker, pre-commit hooks, Makefile, or API routes
- No OpenCode config (`opencode.json`), no Cursor config (`.cursor/` is gitignored)
- No broadcasting/WebSockets (`BROADCAST_CONNECTION=log`)
