# WP9 — Seed the whole pharmacy settings catalogue as visible **«تحت التشغيل»**

**Repo:** BE + FE · **Branch:** `hazemdev2` · **Migration:** **YES** (one column)

## Goal (owner's request, 2026-08-02)

> «عايز تضيفهم كلهم بالشكل الواقعي في إعدادات نقاط البيع، ويبقى جنبهم "تحت التشغيل"، وكل واحد نخلّصه نفعّله — علشان يبقوا موجودين بشرحهم ويبقى أوضح.»

Put the **entire settings catalogue** on the POS settings screen now — every option visible with its bilingual name, its explanation and its group — but each not-yet-built one shown **switched off, locked, and badged «تحت التشغيل»**. As each one's real behaviour lands in a later WP, that same WP flips it to active.

Source of truth for the list, the groups, the wording, the defaults and the scope column:
`/home/moonui2/public_html/knowledge-base/plans/pos-settings-catalogue-and-drug-product.html` §4 (9 groups).

## ⚠️ The hard requirement — this is the whole safety of the feature

A not-yet-implemented setting **must be impossible to turn on**, from the screen *and* from the API.

If it is switchable, a manager will enable «منع بيع الدواء المنتهي», walk away satisfied, and **expired medicine will still sell**. That is strictly worse than the option not existing, because it manufactures false confidence. This project already has a live example of the silent version: `clinic.pharmacy_source` is fully defined, bilingual, visible — and has **zero readers anywhere in the codebase**.

So:
- FE control **disabled** (not merely styled) + a clear badge + a one-line note.
- BE **rejects the write with a 422** naming the key.
- Both. Not either.

## Design

### 1. Schema — one column
Add `is_implemented` **boolean, default `true`** to `setting_definitions` (root or Core migration, guarded with `Schema::hasTable`/`hasColumn` — follow `database/migrations/2026_02_23_102407_add_tracking_type_to_products_table.php`). Default `true` so **every one of the ~200 existing definitions is unaffected**. Only the new catalogue rows ship `false`.

Expose it on `Modules/Core/app/Http/Resources/SettingDefinitionResource.php`.

### 2. The server-side lock — one choke point
`Modules\Core\Services\SettingsService::set()` is the single path every module's settings controller writes through (`SalesSettingController`, `POSSettingController`, the generic `SettingController`, …). Put the guard **there**, next to the existing `validateValue()` call, so every module inherits it and no future controller can bypass it.

A write to a definition with `is_implemented = false` → **422** with a bilingual message naming the key, e.g. *"This setting is not available yet."* / *"هذا الإعداد لسه تحت التشغيل."*
Add the message to `Modules/Core/lang/{en,ar}/core.php` following the existing convention.

**Reads are unaffected** — the setting must still appear in `GET`.

### 3. The screen
In the generic settings renderer (`src/app/features/settings/settings.component.*`, the `@switch (setting.value_type)` block WP1 built for POS and Sales uses):
- when `definition.is_implemented === false`: render the control **`[disabled]="true"`**, dimmed, plus a badge **«تحت التشغيل»** / *"Coming soon"* next to the label, and a small line under the description: «هيتفعّل في تحديث قادم» / *"Will be enabled in a future update."*
- Everything else — label, description, group, ordering — renders exactly as an active setting.
- Add the two i18n keys to **both** `src/assets/i18n/en.json` and `ar.json`, additively, at matching positions.

Apply it in the **shared** renderer, not only in the POS tab — any module can then use it.

### 4. The definitions
Seed the company-scoped settings from §4 of the analysis into `Modules/Core/database/seeders/SettingDefinitionSeeder.php` (the POS block starts ~line 2281; keep `updateOrCreate`, keep it idempotent). For each: `setting_key`, `module`, `value_type`, `default_value`, `allowed_values`, `scope: 'company'`, `label_en`, `label_ar`, `description_en`, `description_ar`, `display_group`, `display_order`, `is_visible: true`, **`is_implemented: false`**.

**The Arabic label and description are the deliverable here** — copy the owner-facing wording from the analysis tables, do not invent new phrasing and do not machine-translate. The explanation is why he asked for this.

Groups (use these `display_group` values so the screen matches the analysis):
`pos_expiry` · `pos_units_pricing` · `pos_prescription` · `pos_patient_insurance` · `pos_returns` · `pos_pharmacist` · `pos_receipt` · `pos_operations` · `pos_integrations`

**Already-existing keys must NOT be re-seeded as coming-soon.** These are live and stay `is_implemented = true`: `inventory.expired_issue_policy`, `inventory.issuing_order`, `inventory.near_expiry_days`, `sales.allow_below_min_price`, `nphies.enabled`, `einvoicing.enabled`, `clinic.pharmacy_source` (already defined; leave its state alone), and the six live `pos.*` keys.
If a catalogue row duplicates one of those, **skip it and say so in your report** — one home per concept.

**Terminal-scoped rows are OUT of this WP.** `setting_definitions` has no terminal scope, and the terminal blob has no description slot. The analysis marks a handful as terminal (unit switcher display, batch-on-line display, receipt paper width, auto-print, copies). **List them in your report as excluded, with the reason** — the owner will decide separately. Do not force them into company scope just to make them visible.

### 5. The activation contract — write it down
Add a short comment block at the top of the new seeder section stating: *a setting flips to `is_implemented => true` in the SAME work package that writes its first reader, never before, and never as a standalone change.* The next agent must not be able to miss this.

## Acceptance criteria

1. Every seeded catalogue setting appears on the POS settings tab with its Arabic label, its Arabic explanation, and in its group.
2. A coming-soon setting's control is **disabled** and badged; an implemented one is unchanged.
3. `PUT` to a coming-soon key → **422**; `PUT` to an implemented key still succeeds. Prove both.
4. `GET` still returns coming-soon settings (they must be visible).
5. **Zero behaviour change for all existing definitions** — they default to `is_implemented = true` and remain editable. Prove with a test that an existing key still saves.
6. Seeder is idempotent; re-running changes nothing.
7. Migration runs clean on dev; `ng build` green.

## Tests

- Extend `Modules/POS/tests/Feature/POSSettingApiTest.php` and/or `Modules/Core/tests/Feature/SettingApiTest.php`: coming-soon write → 422; implemented write → 200; coming-soon appears in the index; existing keys unaffected.
- One test asserting the **default is `true`** so a future definition added without the flag is never accidentally locked.

⚠️ **Pest loads every test file into ONE process** — prefix every top-level helper with its file's subject. A generic name is a fatal redeclare that kills the whole suite (exit 255, zero output). Four occurrences in this project so far.

## Baselines
- `pest --filter='ProductApiTest|ProductUnitApiTest|SettingApiTest|SettingPermissionEnforcementTest|BrandApiTest|UnitConversionServiceTest|UnitBaseValidationTest|POSTerminalApiTest'` → 191 passed / 14 failed (pre-existing).
- `pest Modules/Core` → 686 passed / 10 failed (pre-existing).
- `pest Modules/POS` → 226 passed / 0 failed.
**Green = zero NEW failures.**

## Environment
- BE `/home/moonui2/moon-erp-be`, FE `/home/moonui2/public_html/moon-erp`, branch `hazemdev2`. **moonui2 ONLY — never `/home/moonui`.**
- BE tests: `/opt/cpanel/ea-php82/root/usr/bin/php -d memory_limit=1G vendor/bin/pest --filter='…'`.
- Migration on dev in the same step: `/opt/cpanel/ea-php82/root/usr/bin/php artisan migrate --force`, then run the seeder, then `bash local-deploy.sh`. ⛔ Never `migrate:fresh`/`refresh`/`db:wipe` on `moonui2_dev_be`.
- Build + **deploy to `/app`** when green (owner tests live there). No client ship, no merge, no push, no commit.
- `pint` on touched files only; `chown moonui2:moonui2` after every edit.
- Never `git checkout`/`restore`/`stash` on `src/assets/i18n/{ar,en}.json`; additive edits, verify both parse as JSON.
- Dirty working tree by design — never revert/stash/commit what you did not write.
