# Salihiya World Cargo — Claude Code Guide

> **Read this fully before doing anything.** This file is automatically loaded at the start of every session. It contains rules that must be followed without exception.

---

## What This Project Is

An internal cargo management system for **Salihiya World Cargo Ltd**, Nairobi, Kenya. Staff use it to track shipments arriving from China and Dubai, manage consignee records, issue invoices, record payments, and release goods with delivery notes.

Real money and real client records live in this database. Mistakes here have financial consequences.

---

## Tech Stack

| Layer | Technology |
|---|---|
| Framework | Laravel 12 |
| Admin UI | Filament v5 (Livewire v4 underneath) |
| Database | MySQL — sessions, queue, and cache also in DB |
| PHP | 8.2 |
| Hosting | cPanel shared hosting (`~/clients/salihiya`) |
| Timezone | `Africa/Nairobi` (EAT, UTC+3) — all timestamps are Kenya time |
| Branch | `main` → production |

---

## Project Layout

```
app/
  Filament/Pages/          ← Custom Filament pages (Dashboard, CustomerAccount, etc.)
  Filament/Resources/      ← Filament CRUD resources (Invoices, Payments, Shipments, etc.)
  Filament/Concerns/       ← Shared traits (HasDocumentActions)
  Http/Controllers/        ← DocumentController (invoice/DN/receipt/GDN print)
  Models/                  ← Eloquent models
  Services/
    InvoiceService.php     ← ⚠️ CRITICAL — payment math lives here
    SequenceService.php    ← ⚠️ CRITICAL — race-safe number generators
    ConsignmentImportService.php
    StoreListImportService.php
database/migrations/       ← Never modify existing migration files
resources/views/
  document-invoice.blade.php   ← Invoice print template
  document-print.blade.php     ← DN, receipt, GDN print template
  reports/                     ← Payments and stock register reports
```

---

## ⚠️ Files You Must Never Casually Edit

These files contain carefully calibrated logic. Changing them without fully understanding the context will break financial records or production for real users.

### `app/Services/InvoiceService.php`
The core payment accounting engine. Contains `recalculateInvoiceBalance()` which handles a subtle split between **multi-item invoices** (items.sum is the gross, discount reduces it) and **direct-link invoices** (amount_kes is already the effective, discount is informational). Getting this wrong silently corrupts invoice totals. **Do not touch without reading the full method and its comments.**

### `app/Services/SequenceService.php`
Generates invoice numbers, receipt numbers, delivery note numbers, and GDN numbers using `DB::transaction()` + `lockForUpdate()`. This prevents race conditions when two staff members save simultaneously. **Do not simplify or remove the transaction/lock pattern.**

### `database/migrations/`
**Never modify an existing migration file.** Once a migration has run on production, changing it does nothing locally and causes silent schema divergence. To change the schema, always create a new migration.

### `resources/views/document-invoice.blade.php` and `document-print.blade.php`
These templates render every printed document (invoices, receipts, delivery notes, group delivery notes). A CSS mistake here breaks printing for every user. The `@@media` and `@@page` CSS at-rules use double-`@` escaping — this is intentional Blade syntax, do not change to single `@`.

### `app/Filament/Resources/Invoices/Pages/CreateInvoice.php`
The custom multi-item invoice creation page. Contains a Livewire component with per-item rate locking logic. The `air_rate`, `sea_rate`, and `exchange_rate` fields intentionally do NOT auto-recalculate existing items — this was a deliberate design decision to support mixed-origin invoices (Dubai at $10 + China at $14 on the same invoice).

---

## Business Logic Rules — Know These Before Coding

### Consignment Status Flow
```
in_transit → in_warehouse → ready_for_pickup → delivered
                                              → missing (admin only)
```
- `ready_for_pickup` is set automatically when an invoice is **fully paid**
- `delivered` is set when goods are physically released (Release Goods action)
- Never skip steps in this flow without understanding the downstream effects

### Invoice Types
There are two structurally different invoice types. The math is different for each:

1. **Multi-item invoice** (created via Create Invoice page): Has `invoice_items` rows. `invoice.amount_kes` = `items.sum - discount + vat`. The items table is the source of truth for the gross amount.

2. **Direct-link invoice** (auto-generated from Stock Register): Has no `invoice_items`. `invoice.amount_kes` IS the effective amount already. The discount field is informational only.

`InvoiceService::recalculateInvoiceBalance()` handles both cases differently. **This distinction must be preserved.**

### Payment Write-Off
When forgiving a small remaining balance:
- Multi-item: add write-off amount to `discount_kes` → recalc reduces effective
- Direct-link: subtract write-off amount from `amount_kes` AND add to `discount_kes`
Both paths end with `recalculateInvoiceBalance()` producing `outstanding = 0`.

### Rates
- Global rates: stored in `global_settings` table, managed via Settings page
- Default air rate: `$14/KG` (USD)
- Default sea rate: `KES 63,000/CBM`
- Exchange rate: `~KES 131/$1` (update via Settings when it changes)
- Customer-specific rates: stored in `customer_rates` table (rare)
- Invoice rates **snapshot at the time the consignment is added** — changing the rate field does not recalculate already-added items

### Branches
Users with `branch_id` set only see consignments where `current_branch_id` matches theirs. Users with `branch_id = null` (admin, super_admin, director) see everything. **Always respect this scoping in new queries.**

---

## Filament v5 Patterns — Common Mistakes to Avoid

```php
// ✅ Correct namespace for actions in resource tables/pages
use Filament\Actions\Action;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;

// ❌ Wrong — will throw "Class not found"
use Filament\Tables\Actions\EditAction;

// ✅ Correct — resource Pages use non-static $view
class CreateInvoice extends Page
{
    protected string $view = 'filament.invoices.create'; // non-static
}

// ❌ Wrong — static $view causes errors in Filament v5
protected static string $view = '...';

// ✅ BulkActions must call $action->halt() on failure to stop redirect
->action(function (Collection $records, array $data, BulkAction $action): void {
    if ($somethingWrong) {
        Notification::make()->danger()->send();
        $action->halt(); // ← required, prevents successRedirectUrl from firing
        return;
    }
})
```

---

## Required Workflow

### Before Starting Any Work
```bash
git pull origin main
php artisan migrate          # apply any new migrations
php artisan optimize:clear   # clear stale caches
```

### Making Changes

> **If you are not the project owner (Muhamaddiis), you must follow the branch rule below without exception.**

**Branch rule:**
- `main` → production server. **You cannot push to this branch.** Only the project owner merges to `main`.
- `dev` → the branch you work on. Always start here.
- Feature branches → branch off `dev` for larger pieces of work.

```bash
# Start of every work session
git checkout dev
git pull origin dev

# Create a feature branch for your change
git checkout -b feature/your-feature-name

# When done — push your branch and open a PR to dev, not main
git push origin feature/your-feature-name
# Then open a Pull Request on GitHub: feature/... → dev
```

**Never run:**
```bash
git push origin main        # ← blocked
git push --force            # ← blocked
git reset --hard            # ← blocked
```

When your work is ready for production the project owner will review your PR, merge to `dev`, test it, then merge `dev` → `main`.

### Adding New Images or Static Assets
Images added to `public/images/` must also be manually copied to `public_html/images/` on the production server. The two directories are separate. Run:
```bash
cp ~/clients/salihiya/public/images/<file> ~/public_html/images/<file>
```

### Database Migrations
- Write a **new** migration file — never edit existing ones
- Name: `YYYY_MM_DD_HHMMSS_describe_change.php`
- Test locally with `php artisan migrate`
- On production: `php artisan migrate --force`

### Production Deploy
```bash
git pull origin main
php artisan migrate --force
composer install --optimize-autoloader --no-dev
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
```
Do **not** run `php artisan optimize:clear` on production after caching — that would bust the caches you just built. Only use `optimize:clear` to reset during debugging.

---

## Server Setup (Production)
- App root: `/home/vldeyoiz/clients/salihiya/`
- Web root: `/home/vldeyoiz/public_html/` (separate from app)
- `public_html/index.php` bootstraps the app from `~/clients/salihiya`
- Memory limit: 256M (intentional — higher limits exhaust cPanel per-user budget)
- PHP: 8.2 via cPanel (ea-php82)
- LiteSpeed web server — `.htaccess` `php_value` directives may not apply (LiteSpeed ignores mod_php directives)

---

## Things That Are Intentionally "Weird"

These look like bugs but are deliberate decisions — do not "fix" them:

| Thing | Why it's intentional |
|---|---|
| `@@media print` and `@@page` in blade templates | Blade escaping — single `@` would be parsed as a Blade directive and crash |
| Air rate and sea rate don't auto-recalculate existing items | Supports mixed-origin invoices (Dubai $10 + China $14 on same invoice) |
| `QUEUE_CONNECTION=sync` | No queue worker runs on shared hosting; jobs run inline |
| `memory_limit = 256M` | Higher values reduce concurrent PHP workers → more 508 errors |
| `position: static !important` on `.toolbar` in print CSS | Prevents Chrome blank-first-page bug caused by `position: fixed` elements |
| `invoice.amount_kes` behaviour differs between invoice types | Multi-item: gross; direct-link: effective. Both are correct for their type |

---

## What Requires Human Review Before Merging

Any PR that touches these must be reviewed by the project owner before merging:

- `app/Services/InvoiceService.php`
- `app/Services/SequenceService.php`
- Any `database/migrations/` file
- `resources/views/document-invoice.blade.php`
- `resources/views/document-print.blade.php`
- `app/Filament/Resources/Invoices/Pages/CreateInvoice.php`
- `config/app.php` (timezone changes affect all stored timestamps)

---

## Permissions & Roles (Quick Reference)

| Role | Can do |
|---|---|
| `super_admin`, `admin` | Everything |
| `director` | Read-only dashboard, branch P&L reports |
| `branch_manager` | All operations scoped to their branch |
| `cashier` | Record payments, view invoices |
| `warehouse` | Stock Register (ready_for_pickup only), release goods |
| `staff` | View consignments, generate delivery notes |

Warehouse agents see **only `ready_for_pickup` goods** — this is enforced at the query level in `StockRegisterResource::getEloquentQuery()`, not just as a filter. Do not remove this restriction.

---

## Running Tests

```bash
php artisan test
```

There is currently no test suite. When adding tests:
- Place in `tests/Feature/` for end-to-end flow tests
- Focus on: invoice math, payment recording, consignment status transitions
- Never use mocks for database operations — use real DB with test transactions

---

## Quick Checklist Before Every Commit

- [ ] Does `php artisan optimize:clear` complete without errors?
- [ ] If I added a new migration, did I test it locally?
- [ ] If I changed invoice/payment math, did I verify the numbers manually?
- [ ] If I changed a print template, did I check the print preview in browser?
- [ ] Am I on a feature branch, not `main`?
