Commit f114bf42 authored by Claude's avatar Claude

docs: move agent rules out of auto-injection, compact CLAUDE.md

The 18 files in .claude/rules were injected into every agent turn — 38 KB of
POS, inventory and attendance rules loaded even while editing CSS. They are now
in docs/agent-rules/ and read on demand.

CLAUDE.md keeps every hard invariant inline (money as piasters, tenancy scoping,
double-entry immutability, migration-first, RTL logical properties, no dead
links, safe_url/clean_html) and indexes the detail, so nothing that protects
code quality was dropped.

Also documents the deployment constraint that governs every migration: all
tenants build from main and the entrypoint runs migrate --force plus db:seed on
every container start.

Net: ~57 KB less agent context per turn.
Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
parent fa497ab9
# El Captain Sports Management
## What This Is
A modular ERP specialized for sports organizations. Activity-agnostic platform — any sport/activity is a configuration of the same underlying business model.
Modular ERP for sports organisations. Activity-agnostic: any sport is a
configuration of the same business model.
## Stack
- **Framework:** Laravel 13 (PHP 8.5)
- **Database:** PostgreSQL 16 at `18.192.166.221:5434` (db: `elcaptainsportsonly`, user: `elcaptain`)
- **Frontend:** Livewire 3 + Alpine.js + Tailwind CSS (to be added)
- **Multi-tenancy:** Single database with `academy_id` column + global scope
- **Deploy:** CapRover at `*.caprover.al-arcade.com`
**Stack:** Laravel 13 / PHP 8.4 · PostgreSQL 16 · Livewire 3 + Alpine + Tailwind ·
Arabic-first (`ar` default, bilingual) · CapRover deploy.
## Architecture
**Layout:** `app/Domain/{Module}/{Models,Services,Events,Enums}`,
`app/Domain/Shared/Traits`, `app/Livewire/{Feature}`, `resources/views/website`.
### Domain-Driven Layout
```
app/Domain/{Module}/Models/ — Eloquent models
app/Domain/{Module}/Services/ — Business logic (DB::transaction wrapped)
app/Domain/{Module}/Events/ — Domain events (dispatched after commit)
app/Domain/{Module}/Enums/ — PHP 8.1+ backed enums
app/Domain/Shared/Traits/ — BelongsToAcademy, HasUuid
```
---
## Deployment reality — read before writing any migration
Every client is **one CapRover app + one dedicated Postgres DB**, all building
from git **`main`**. There is no staging. `docker/entrypoint.sh` runs
`migrate --force` **and** `db:seed` on **every container start**.
Therefore, non-negotiable:
- Migrations are **additive**; guard every `up()` with `hasTable`/`hasColumn`.
- Destructive operations live in `down()` only — never in `up()`.
- Seeders must be **idempotent** and must never contain client-specific content.
- Any migration must be safe against every existing populated client DB.
## Hard invariants (violating these breaks production)
**Money** — all amounts are `bigInteger` **piasters**, never decimal/float.
150.00 EGP = `15000`. Convert only in `format_money()` for display. Integer
arithmetic only; when splitting, round down and give the remainder to the last item.
**Tenancy** — every domain model uses `BelongsToAcademy`. Every tenant table has
`academy_id`. Every unique constraint **includes `academy_id`**. Only
`academies`, `permissions`, and framework tables are exempt. SuperAdmin is the
only code path that may bypass the global scope.
**Financial** — every movement writes a **debit + credit pair**, amounts always
positive, `type` carries direction. `transactions` and `audit_logs` are
**immutable**: no `updated_at`, no soft deletes, corrections are new reversing
entries. Invoice totals freeze at creation.
**Inventory** — never touch `inventory_levels.quantity_on_hand` directly. All
changes go through `InventoryService::createMovement()`, which locks the row and
records before/after. Stock may reach 0, never negative.
**Pricing** — no fixed prices; every price is computed at sale time. **No active
base price = hard fail** (throw, block the sale, show `لا يوجد سعر محدد`).
Never default to 0 or guess. Prices on an issued invoice are frozen forever.
**Schema** — migration first, always. Model `$fillable`/`$casts` are copied from
the migration, never typed from memory. Status/type columns get a CHECK
constraint whose values match the PHP enum **character for character**.
**Services** — all writes in `DB::transaction`. Never call `auth()`, `request()`
or `session()` inside a service; receive explicit params. Dispatch events for
side effects; never send mail/SMS/notifications inline. Inject collaborators via
the constructor.
### Key Patterns
- **BelongsToAcademy trait** — auto-scopes queries to `app('current_academy')`
- **HasUuid trait** — auto-generates UUID, uses `uuid` as route key
- **Amounts stored as integers** (piasters, not pounds) to avoid float issues
- **Double-entry transactions** — every financial operation creates debit + credit entries
- **Events for side effects** — never send email/notification inline from services
### Modules (in build order)
1. Financial (DONE - schema + models + services)
2. People (next)
3. Training Programs + Groups
4. Facility & Scheduling
5. Attendance
6. Inventory
7. Pricing Engine
**Status machines** — every stateful entity declares `VALID_TRANSITIONS` and
throws `InvalidStatusTransitionException` on an illegal move.
**Permissions** — gate at all five points: route middleware, Livewire `mount()`
`authorize()`, service scope check, Blade `@can`, and the list query scope. No
permission means the element is **not rendered**, never greyed out.
**Livewire** — lists use `WithPagination` + `#[Url]` + server-side pagination.
Forms authorize in `mount()`, have `rules()` matching the migration and
`messages()` in Arabic, wrap service calls in try/catch, and put loading states
on every submit button.
**RTL / Arabic** — Arabic is the default. Always use logical Tailwind properties
(`ms/me/ps/pe`, `text-start/end`, `start-*/end-*`, `border-s/e`), never
`ml/mr/pl/pr/left/right`. Numeric inputs get `dir="ltr"`. All user-facing strings
go through `__()`. Font: Cairo.
**Links**`href="#"` is forbidden. Use `route()`; if a feature isn't built,
don't render the link. Editor-supplied URLs must pass through `safe_url()`.
**Security** — any HTML from an editor renders through `clean_html()`. Never
interpolate a value into an Alpine expression with quotes — use `@js()`.
## Detailed rules
Full versions live in `docs/agent-rules/`**read the relevant file before
working in that area**, don't rely on the summary above:
| File | Covers |
|---|---|
| `01-migration-first` `16-enums-and-checks` | schema, CHECK constraints, full enum registry |
| `02-multi-tenancy` `10-permissions` | scoping, roles, permission format |
| `03-money-handling` `05-financial-integrity` | piasters, double-entry, invoices, refunds |
| `04-services-and-events` `06-status-transitions` | service pattern, state machines |
| `07-inventory-movements` `13-pos-workflow` | movement types, the 10-step POS flow |
| `08-rtl-arabic` `09-livewire-alpine` | RTL, component structure, Alpine scoping |
| `11-attendance-engine` `18-space-collision` | attendance rules, collision detection |
| `12-pricing-engine` | 10-step resolution order, 13 rule types |
| `14-build-order` `15-integration-checks` | build sequence, pre-commit checklists |
| `17-audit-and-notifications` | audit immutability, notification flow |
Architecture docs: `docs/00-*``docs/10-public-website-builder.md`.
## Commands
```bash
php artisan serve # Dev server at :8000
php artisan migrate # Run migrations
php artisan db:seed # Seed default data
php artisan tinker # REPL
php artisan serve
php artisan migrate
php artisan tinker
php artisan website:blueprint export|import --file=
```
## Login Credentials (dev)
- Email: `admin@oc-sport.com`
- Password: `Alarcade123#`
## Rules
- Laravel Discipline is ALWAYS active (see global CLAUDE.md)
- Migration first — schema is source of truth
- One vertical slice at a time
- Arabic locale default (`ar`), bilingual (name + name_ar)
- CHECK constraints on all status/type columns
- No `href="#"` — real routes or don't render the link
Dev login: `admin@oc-sport.com`
## Working style
- One vertical slice at a time: migration → model → service → Livewire → view → verify.
- Verify before claiming done. Run the command, show the output.
- Keep tool output small: `grep -o` for names, `head -c` (not `head -n`) for
minified payloads, and never `cat` a file you only need one symbol from.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment