# AGENTS.md — SUBS (Season Parking Subscription System)

> Agent-focused context for coding, debugging, and extending this Laravel application.

## Project Overview

SUBS is a season parking subscription management system built with Laravel 12. It handles customers, parking lots, subscriptions, monthly invoices, payments, and LHDN MyInvois e-invoice submission.

- **Domain (prod):** `cpers-imejparking.com`
- **Local dev URL:** `http://subs.test`
- **Author:** zulfazdliabuas@gmail.com
- **Laravel:** 12.x, **PHP:** ^8.2
- **Frontend:** Bootstrap 5.3.3, Bootstrap Table 1.23.5, Bootstrap Icons 1.11.3, Select2 4.1.0, Tailwind via Vite

## Tech Stack

- **Framework:** Laravel 12
- **Auth:** Laravel Breeze + custom role/permission system (`spatie/laravel-permission` style tables but custom implementation)
- **PDF:** DOMPDF 2.0
- **QR Code:** endroid/qr-code 4.8
- **Queue:** Database driver (`php artisan queue:work`)
- **Scheduler:** Laravel scheduler (`php artisan schedule:run`)
- **Mail:** SMTP (currently Gmail: `zulfazdliabuas@gmail.com`)

## Project Conventions

### Soft Delete & Audit Columns

Every table MUST have these 6 columns:

- `created_user_id`
- `update_user_id`
- `delete_status` (string, e.g. 'yes'/'no' or active/inactive)
- `delete_user_id`
- `delete_time`
- keep existing `created_at` / `updated_at`

Implementation:

- Trait: `app/Traits/AuditableSoftDelete.php`
- Applied to all models
- Global scope excludes records where `delete_status = 'yes'`
- No hard deletes — always mark with audit fields

When writing migrations or models, ensure the trait is used and ambiguous column references use `$table . '.delete_status'`.

### UI Conventions

- **Bootstrap Table** for all list pages.
- Use `fixed-table-toolbar` pattern: toolbar div on the left, Bootstrap Table search aligned right (`searchAlign: 'right'`).
- Toolbar container must have `id="tableToolbar"` and be referenced in bootstrapTable options via `toolbar: '#tableToolbar'`.
- Filter cards are generally hidden but code kept (commented out).
- "Assign Sites" UI hidden but backend kept.
- Lot No. dropdowns use Select2 with `minimumResultsForSearch: 0` so search box always visible.
- Tables must NOT show database ID columns.
- Alerts should be dismissible (`alert-dismissible`).
- Load Bootstrap Icons CSS explicitly in pages that use `bi-*` icons.

### Roles & Permissions

| Role | Description |
|------|-------------|
| `root` | Session-based maintenance root (login: `root@subs.local`, configured via `SYSTEM_ACCESS=true`). NOT a DB role. |
| `admin` | Full admin access |
| `ao` | Admin Officer — approve subscriptions, generate invoices, approve payments |
| `spv` | Supervisor — generate invoices, mark paid |
| `ae` | Account Executive — view/preview invoices |

Common permission variables in views:

```php
$canGenerateInvoice = $viewer?->hasAnyRole(['root', 'admin', 'ao', 'spv']);
$canMarkPaid        = $viewer?->hasAnyRole(['root', 'admin', 'ao', 'spv']);
$canApprovePaid     = $viewer?->hasAnyRole(['root', 'admin', 'ao']);
$canApproveSubscriptions = $viewer?->hasAnyRole(['admin', 'ao']);
```

## Core Workflows

### Subscription Lifecycle

1. **pending** — waiting AO approval
2. **approved** — approved, waiting first invoice generation
3. **active** — billing in progress
4. **expired** — past `end_month`
5. **cancelled** — cancelled

Key fields:

- `commencement_month` — start month
- `duration_months` — subscription duration in months
- `end_month` — calculated: `commencement_month + duration_months`
- `next_invoice_date` — next invoice to generate; set to `null` when expired

### Invoice Lifecycle

Invoice document types:

1. **Billing Invoice** — unpaid invoice sent to customer for payment
2. **Payment Receipt** — generated after payment is approved
3. **E-Invoice** — LHDN-validated document

Payment flow:

1. Send billing invoice email to customer
2. Customer pays
3. Admin marks invoice as paid (only allowed if `emailed_at` is set)
4. Admin approves payment → receipt generated and emailed
5. If customer requests e-invoice → submit to LHDN → send validated e-invoice

Invoice statuses (`invoice_status`):

- `unpaid`
- `paid`
- `cancelled`

E-invoice statuses (`status`, default `NULL`):

- `NULL` — no request yet
- `new_request`
- `approved`
- `waiting_validation`
- `validated`
- `failed`
- `einvoice_sent_to_customer`
- `cancelled`

### Invoice Generation Rules

- **Manual button** (`generateFirstInvoice`): generates ONE invoice for `next_invoice_date` and advances by 1 month. Blocked if `next_invoice_date > end_month`.
- **Cron** (`subscriptions:generate-renewals`): daily 00:10, processes subscriptions where `next_invoice_date <= today` OR `end_month < today`. Expires subscriptions past `end_month`.
- **Auto-expire** (`subscriptions:auto-expire`): daily 00:05, expires active subscriptions where `end_month < today`.

### Public E-Invoice Request Flow

- Customer receives email with invoice PDF and link.
- Link goes to `/invoice/status?invoice_no=...`.
- Customer clicks "Request E-Invoice", can edit address and search TIN.
- Duplicate requests rejected if `customer_requested_at` already set.
- Backup page: `/request?invoice=INV-XXX`.

## Code Locations

| Feature | Location |
|---------|----------|
| Subscription CRUD | `app/Http/Controllers/Admin/CustomerSubscriptionController.php` |
| Invoice CRUD | `app/Http/Controllers/Admin/InvoiceController.php` |
| Invoice actions (send, mark paid, approve, submit LHDN) | `app/Http/Controllers/Admin/RequestController.php` |
| LHDN client | `app/Services/LhdnClient.php` |
| Invoice PDF generation | `app/Services/InvoicePdfService.php` |
| Subscription invoice generation | `app/Services/SubscriptionInvoiceService.php` |
| QR code service | `app/Services/QrCodeService.php` |
| Public invoice status/request | `app/Http/Controllers/PublicRequestController.php` |
| Console commands | `routes/console.php` |
| Scheduler registration | `bootstrap/app.php` |
| Invoice views (PDF/receipt/embed/preview) | `resources/views/invoice/` |
| Admin subscription views | `resources/views/admin/customer-subscriptions/` |
| Admin invoice views | `resources/views/admin/invoices/` |
| Admin request views | `resources/views/admin/requests/` |

## Important Gotchas

1. **Route model binding:** method parameter name MUST match route parameter name (e.g. `{invoice}` route needs `$invoice`, not `$parkingRequest`).
2. **Queue worker required:** Emails are queued. Run `php artisan queue:work` in production. File: `start_queue_worker.bat`.
3. **Invoice email before payment:** `Mark Paid` button only appears if `emailed_at` is set.
4. **Single-site mode:** `config('subs.single_site_mode')` is `true`; Site column hidden from subscription list.
5. **Next invoice date null:** When a subscription is expired, `next_invoice_date` is set to `null`. UI logic must treat `null` as "no more invoices to generate" and show Extend.
6. **End month comparison:** Use `startOfMonth()` comparisons. `greaterThan()` on full Carbon objects is fine because both are stored as start-of-month dates.
7. **Receipt number:** Format `SUBS-YYYY-MM-XXXX`, resets monthly, 4-digit running number.
8. **Request window:** E-invoice request window ends 3 days after `payment_date` (`request_window_ends_at`).
9. **Public preview access:** Backoffice users (admin/ao/ae/spv/root) can preview any paid invoice; customers only their own.
10. **LHDN buyer address:** Uses real `buyer_address_*` fields from invoice, not hardcoded values.

## Commands Reference

```bash
# Run queue worker (keep open)
php artisan queue:work

# Generate renewal invoices manually
php artisan subscriptions:generate-renewals --force

# Expire subscriptions past end month
php artisan subscriptions:auto-expire

# Cancel overdue invoices
php artisan invoices:auto-cancel-overdue

# Run scheduler (for cron testing)
php artisan schedule:run

# Clear caches after deploy
php artisan optimize:clear
php artisan view:clear
```

## Deployment

See `DEPLOY.md` for full server deployment steps. Key points:

- Prod domain: `cpers-imejparking.com`
- Laravel app sits in `public_html`; public path overridden in `index.php` via `$app->usePublicPath(__DIR__)`.
- Build assets with `npm run build` and upload `build/` folder to server.
- After deploy: `php artisan optimize:clear`.

## Local Dev Quick Start

```bash
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan db:seed        # if seeders exist
npm install
npm run dev                # or npm run build
php artisan serve
php artisan queue:work     # separate terminal
```

## Testing Credentials (local)

| Email | Role | Password |
|-------|------|----------|
| admin@system | admin | 12345678 |
| azli@imej.com.my | admin | 12345678 |
| zuraini@imej.com.my | ao | 12345678 |
| mukhlis@imej.com.my | ae | 12345678 |
| yusfaisalyusof83@gmail.com | spv | 12345678 |

Root login: `root@subs.local` / `Root123!` (requires `SYSTEM_ACCESS=true` in env).

## When Modifying Code

- Follow existing code style (author header comments, indentation).
- Keep changes minimal.
- Update corresponding views if controller logic changes.
- If adding a new table, add the 6 audit columns and use `AuditableSoftDelete` trait.
- Clear view cache after blade changes: `php artisan view:clear`.
- Test role-based UI by logging in as different roles.
