# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this directory is

`/home/moonui4/public_html` is the **web root of the `moonui4` account** — a development environment for **Moon ERP**, a multi-module Arabic/English ERP (accounting, inventory, sales, HR, LIS/laboratory, HIS/clinic, manufacturing, POS, e-store). It is not itself a repo; it hosts several independent ones:

| Path | What | Git |
|---|---|---|
| `moon-erp/` | Angular 21 frontend | `hazemhamdytaha/moon-erp-angular`, branch `hazemdev4` |
| `../moon-erp-be/` | Laravel 12 backend (served via the `moon-erp-be` symlink → its `public/`) | `tahadeveloper/moon-erp`, branch `hazemdev4` |
| `knowledge-base/` | Canonical project context — decisions, topics, plans | local repo, **no remote, never pushed** |
| `his-analysis/` | Bilingual HTML gap-analysis / investigation reports (working output) | not versioned |
| `app/` | **Build artifact** — the deployed Angular SPA. Never hand-edit; regenerate via build+deploy | — |

Live at `https://moonui4.elbaset.com` — SPA at `/app/`, API at `/moon-erp-be/api`.

## 🔴 Parallel-dev isolation — read before running anything

This host is **one of several parallel Moon ERP instances** (`moonui`, `moonui2`, `moonui3`, `moonui4`) that share the same two GitHub repos and merge into the same `main`. Each has its own branch, database, and deploy dir. **Everything here is moonui4:**

| | Value |
|---|---|
| Branch (both repos) | `hazemdev4` |
| Dev database | `moonui4_dev_be` |
| Deploy target | `/home/moonui4/public_html/app` |
| Ownership | `chown -R moonui4:moonui4` |

⛔ **Never run `migrate:fresh`, `migrate:refresh`, or `db:wipe`** — the dev DBs are not binlogged, so hitting the wrong one is an unrecoverable wipe.

**Docs and shared commands in this workspace frequently hard-code another instance's values.** Known offenders — verify before trusting any of them:

- `moon-erp/CLAUDE.md` — all paths say `/home/moonui/…`; its API base `https://moon-erp.elbaset.com/api` is wrong here (see runtime config below).
- `moon-erp/e2e/playwright.config.ts` — `E2E_BASE_URL` defaults to `moonui.elbaset.com`; `e2e/README.md` paths are moonui.
- `moon-erp/.githooks/pre-push` — `MOON_BE_DIR` defaults to `/home/moonui2/moon-erp-be`.
- `moon-erp/src/assets/config.json.example` — **committed with unresolved merge-conflict markers** (not valid JSON) and a stray `moonui3` URL.
- `.claude/commands/fullpush.md` — has silently reverted to a sibling account's values **twice**. Verify every run:
  ```bash
  grep -c 'moonui[123]\|hazemdev[123]' .claude/commands/fullpush.md   # expect exactly 2
  ```
  (the 2 are the "never touch /home/moonui{,2,3}" constraint line). Anything higher means it reverted.

Sanitize tokens in any git output — both `origin` URLs embed plaintext GitHub PATs:
`git remote -v | sed -E 's#https://[^@ ]*@#https://***@#g'`

## Read the KB before the code

`knowledge-base/` is the source of truth for **context and decisions** (code remains source of truth for behavior). Standing protocol for any substantive task: read `knowledge-base/INDEX.md` → open the relevant `topics/<slug>.md` → *then* the code. After the work, bump the topic's `updated:` and add a line under `Decisions` / `Open` — and always before a `/clear` or `/compact`.

`topics/` are Markdown with mandatory front-matter (`title, slug, status, owner, updated, refs, plans, related`) and fixed sections (`Context` · `How it works` · `Decisions & why` · `Gotchas` · `Open / next` · `Links`). `plans/` holds ~160 mostly-HTML plan/analysis docs; large multi-work-package efforts get a subdirectory with a live `RESUME.md` or `LEDGER.md` execution-state file. Content is bilingual (Egyptian Arabic prose, English technical terms). Dates are absolute — never "today".

## Per-repo docs (don't duplicate; do sanity-check)

- Backend: `../moon-erp-be/CLAUDE.md` — first ~253 lines are auto-generated Laravel Boost guidelines; project content follows. `AGENTS.md` is a subset. `.ai-instructions.md` holds the 12-step Moon Support ticket workflow.
- Frontend: `moon-erp/CLAUDE.md`, plus per-module files at `moon-erp/src/app/core/CLAUDE.md` and `moon-erp/src/app/features/{lis,accounting,hr,sales,purchases,inventory,pos}/CLAUDE.md` — **read the module file before touching a module.**

⚠️ Both root files carry stale claims. Verified corrections:

*Backend* — it is **Laravel 12 / PHP 8.2** (not 11/8.3). Modules use nwidart v11+ layout (`app/`, `routes/`, `tests/` — not `Entities/`, `Routes/`, `Tests/`). Routes have **no `{tenant}` and no version segment**. There is **no company-prefixed-table tenancy, no `$baseTable`, no `TenantMiddleware`, no `config/tenancy.php`**. Modules are `Purchases` (not "Purchasing"); there is no "Reports" module. `spatie/laravel-translatable`, medialibrary, and excel are **not** installed.

*Frontend* — tests are **not** "disabled/none configured" (see commands below). Directory counts are understated (258 services, 32+ store dirs, 89 features). Undocumented but load-bearing: `ownerGuard` / `confinementGuard` / `moduleGuard`, the interceptor retry policy, the `X-Branch-Id` header, `I18N_VERSION`, and the `/lab` `/pos` `/clinic` `/factory` top-level route trees.

## Backend — `../moon-erp-be` (Laravel 12, PHP 8.2)

17 nwidart modules, all enabled (`modules_statuses.json`): Core, Accounting, Inventory, Sales, Purchases, POS, HRM, LIS, WebStore, Production, CRM, CMMS, QMS, NPHIES, EInvoicing, Clinic, E2eRunner. LIS (783 files) and Clinic (573) are the largest.

Module autoloading is via `wikimedia/composer-merge-plugin` over `Modules/*/composer.json` — **adding a module requires `composer dump-autoload`.**

**Routing.** Each module's `app/Providers/RouteServiceProvider.php` registers `api/<prefix>` under `['api','auth:sanctum']` and `<prefix>` under `['web','auth']`. Prefix ≠ module name in one case: `HRM` → **`api/hr`**. `api/auth` is public (`throttle:30,1`). LIS adds `api/lis/portal` and `api/lis/external-lab-portal` with separate guards; WebStore splits public/customer/admin. Per-controller authorization uses Laravel 12 `HasMiddleware` with `permission:<module>.<resource>.<action>`.

**🔴 Tenancy is manual and unguarded.** `app/Traits/TenantAware.php` only stamps `company_id` on create and offers an opt-in `scopeTenant()`. There are **zero Eloquent global scopes** — every query must carry `->where('company_id', auth()->user()->company_id)` explicitly (the pattern appears in 2,482 module files). A forgotten filter leaks cross-company data with no safety net. This is the single biggest correctness hazard in the backend; treat it as a review checklist item on every query you write.

**Auth.** Sanctum personal access tokens that **never expire** (`expiration => null`). Guards: `web` (staff) and `store-customer` (WebStore). `ResolveAuthToken` middleware restores the bearer token from `X-Authorization` or `?token=` because cPanel/ModSecurity strips `Authorization` — this is why the frontend sends `X-Authorization`. Roles via spatie (`teams => false`): `owner` holds every permission, `super-admin` bypasses Gate entirely. Staff middleware subclasses reject non-staff tokens with 403 before spatie runs.

```bash
cd /home/moonui4/moon-erp-be
vendor/bin/pint --dirty --format agent      # REQUIRED before finalizing any change
php artisan test --compact                  # all (Pest 3 / PHPUnit 11, sqlite :memory:)
php artisan test --compact --filter=testName
php artisan test Modules/Accounting/tests/Feature/AccHigh4TrialBalanceTest.php
php artisan test --testsuite=Unit
php artisan make:test --pest {name}
bash local-deploy.sh                        # dump-autoload + optimize:clear + migrate --force (if pending)
```

611 module tests live in `Modules/*/tests/{Feature,Unit}`, named by issue ID (`AccHigh4TrialBalanceTest`). `phpunit.xml` hard-overrides `APP_URL=http://localhost` because the live `.env` serves from the `/moon-erp-be` subpath, which would 404 every HTTP test. Roles/permissions are seeded once per process via `RolePermissionSeeder` (outside the per-test transaction). Never `env()` outside `config/`; always Form Requests, never inline validation; prefer `Model::query()` over `DB::`.

Scheduler (`routes/console.php`): license health/errors/commands/sync on 15/5/1/5-minute cadences, `accounting:integrity-check` daily 02:30, `lis:repost-b2b-items` hourly.

Production deploys (`deploy-background.sh`, branch `prod`, webhook-triggered) deliberately **never cache routes** (~1183 routes exceed shared-hosting memory) and **must** run `event:clear && event:cache` — a stale `bootstrap/cache/events.php` silently kills new listeners.

## Frontend — `moon-erp` (Angular 21, standalone, NgRx)

Standalone components throughout — no NgModules. `src/app/app.config.ts` is the single provider manifest (~55 eagerly-registered NgRx slices + effects, PrimeNG Aura theme with CSS-layer order `theme, base, primeng, components, utilities`, translate service, a `ThemeService.init()` initializer). `src/app/app.routes.ts` has 190 routes, all lazy, across the trees `''` (main authed layout), `/lab`, `/pos`, `/clinic`, `/factory`, `/portal`, `/external-lab-portal`, `/p/:token`, `/catalog`.

Data flow: `Component → NgRx Action → Effect → Service → API → Effect → Reducer → Selector → Component`. Every slice is `@ngrx/entity` with `{ meta, loading, saving, error, loaded }`. Tailwind 4 (no config file — tokens live in an `@theme` block in `src/styles.scss`) + PrimeNG 21. No `paths` mapping in tsconfig — imports are relative.

```bash
cd /home/moonui4/public_html/moon-erp
npx ng serve                       # localhost:4200
npx ng build --base-href /app/     # output → dist/moon-erp/browser/
npm run test:reports               # LIS report contract + snapshot harness
npm run test:e2e                   # Playwright, drives the DEPLOYED app
npx ng generate component features/<name> --standalone --style=scss --skip-tests
```

`npm test` / `ng test` / `ng e2e` **do not work** — angular.json has no `test` or `e2e` target and there are zero `.spec.ts` files under `src/` (README advertising them is stock CLI boilerplate). The two real suites:

- **`npm run test:reports`** → `scripts/report-contract.mjs`. esbuild-bundles the real `lis-html-report.service.ts` for Node (Angular DI shimmed), renders 5 report templates × a fixture matrix, asserts build-breaking contracts (signature gating, QR, DRAFT watermark, footer, comments, histo order) and 30 normalized-HTML snapshots. Refresh with `node scripts/report-contract.mjs --update`.
- **`npm run test:e2e`** → Playwright against the **deployed** app, not a dev server. On this account you must override the base URL:
  ```bash
  E2E_BASE_URL=https://moonui4.elbaset.com/app npx playwright test --config e2e/playwright.config.ts
  # single file
  ... --config e2e/playwright.config.ts specs/lis-request-flow.spec.ts
  # single test by title
  ... --config e2e/playwright.config.ts -g "creates a lab request"
  ```
  Uses system Chromium (`/usr/bin/chromium-browser`) — **do not run `npx playwright install`**; the bundled browsers are root-owned and unreadable. Writes to a dedicated write-isolated company (`E2E Test Lab`, id 8) so real data is never touched.

### Frontend traps that have each caused a real bug

**API base is runtime config, not an environment file.** Both `environment.ts` files set `apiUrl: ''` *on purpose* so a broken config fails loudly. `main.ts` awaits `loadAppConfig()` before bootstrap, which fetches `assets/config.json` and mutates `environment.apiUrl`. `src/assets/config.json` is **gitignored and untracked** (each instance keeps its own; `config.json.example` is the tracked template). Here it must read `https://moonui4.elbaset.com/moon-erp-be/api`. A wrong or missing file at build time silently points `/app` at another instance's backend — so **always verify the live file after deploy, not the source.**

**`I18N_VERSION` must be bumped by hand.** Translations load at runtime from `/assets/i18n/{ar,en}.json` with a `?v=${I18N_VERSION}` suffix defined in `app.config.ts`. These JSON files are not fingerprinted, so without a bump the browser serves the stale cached file and every newly-added key renders as its raw path (`CLINIC_QUEUE.SHOWING`) on the client. Bump it on **any** edit to `ar.json` / `en.json`.

**Auth header is `X-Authorization: Bearer <token>`**, not `Authorization` (see the backend `ResolveAuthToken` note). The interceptor skips `/assets/`, `.json`, portal routes, and login/register; it adds `X-Branch-Id` only on `/lis/` URLs when a LIS branch filter is active; and it auto-retries GET/HEAD only — `502/503/504` with `[2,4,8,15]s` backoff (riding a MoonStack update window so screens don't blank) and `0/408/425/429` with `[1,2,3]s`, jittered, excluding 500 and other 4xx.

**The API caps page size at 25 and ignores `per_page`** — services use `listAll()` + `forkJoin`.

## Build & deploy the frontend to `/app`

The owner tests on the live `/app`, so **deploy after every frontend change.** There is no deploy script; it is this sequence (also step 8 of `/fullpush`):

```bash
cd /home/moonui4/public_html/moon-erp
# 1. confirm src/assets/config.json apiUrl == moonui4
# 2. bump I18N_VERSION in src/app/app.config.ts if ar.json/en.json changed
npx ng build --base-href /app/
rm -f /home/moonui4/public_html/app/*.js /home/moonui4/public_html/app/*.css \
      /home/moonui4/public_html/app/*.html /home/moonui4/public_html/app/*.ico
\cp -rf dist/moon-erp/browser/* /home/moonui4/public_html/app/
chown -R moonui4:moonui4 /home/moonui4/public_html/app
curl -s https://moonui4.elbaset.com/app/assets/config.json | grep apiUrl   # must say moonui4
```

`\cp -rf` bypasses the `cp` alias. The `rm` clears accumulated hashed chunks — keep it globbing only top-level `*.js/css/html/ico`, because `app/.htaccess` lives **only** in the deploy target (not in the repo) and must survive. That file provides the SPA fallback under `/app/`, year-long immutable caching for hashed assets, and `no-store` on `index.html`. `mod_proxy` is not available in `.htaccess` on this cPanel host.

The root `public_html/.htaccess` is only the cPanel `ea-php82` handler — routing comes from the filesystem layout plus `app/.htaccess`.

## Release notes & the pre-push hook

Both repos activate `.githooks/pre-push` via `git config core.hooksPath .githooks` (wired by the backend's `local-deploy.sh` and the frontend's `npm postinstall`). The product changelog lives in the **backend** at `docs/moonstack/CHANGELOG.md`.

- **Backend hook soft-blocks** a push that changes user-facing source without an added `## [Unreleased]` bullet.
- **Frontend hook warns only** (never blocks) — it looks for the backend's bullets via `MOON_BE_DIR`, which defaults to another account here.

User-facing work needs a bilingual bullet under `## [Unreleased]`: `- **Title.** … {{ar}} **العنوان.** …`. Internal/refactor/tooling work bypasses with `[skip-changelog]` in a commit message or `SKIP_CHANGELOG=1 git push`. **Do not use `--no-verify`** — it is invisible, whereas the token is auditable via `git log --grep`.

Backend changelog entries also carry an issue ID `[MODULE-NNN]`; the backend CLAUDE.md documents a mandatory multi-surface doc-update workflow (CHANGELOG + `docs-site` JSON + counter + Scribe/OpenAPI regeneration) for ticket work.

## Working conventions

- **Study before implementing.** For any non-trivial feature, fix, or investigation, produce a bilingual HTML analysis in `his-analysis/` (sections: المشكلة · الوضع الحالي · المطلوب · الفجوة · الملفات المتأثرة · الحالات الحدّية · خطة التنفيذ كـ Work Packages · قرارات تحتاج المالك · معاينة الواجهة · التوزيع) and get owner approval **before** writing code.
- **Fix in the shared layer.** A defect spanning multiple documents/screens gets one fix in the shared trait/service/component they all use — never screen-by-screen.
- **Bilingual data.** Every model with `name` needs `name_ar`; every module needs `lang/ar` + `lang/en`. Frontend fields are `name_ar`/`name_en` (or `title_ar`/`title_en`); translation keys are `FEATURE_NAME.FIELD`. Money is `decimal(12,3)`.
- **Never ship to clients.** Work here stops at the moonui4 dev environment and the `hazemdev4` / `main` branches. The signed production release is the owner's separate step.
- `/fullpush` (`.claude/commands/fullpush.md`) automates the full sync: commit+push `hazemdev4` → merge `origin/main` (CHANGELOG `[Unreleased]` conflicts are **always a union** of both sides) → merge to `main` → migrate+seed `moonui4_dev_be` → rebuild and deploy `/app`. Grep-verify its account values first (see the isolation section).
