# W6-BE-F — GYN complaints: own category + Arabic labels + specialty-first order (Fable P1-3)

Date: 2026-10-23 · implementer: W6-BE-F · not committed

## Root cause
`GynFieldPackSeeder::seedLookup` created the 20 `gyn_schema.json` complaints as `clinical_lookups`
(`lookup_key=complaint`, `specialty=gyn_ob`) with `parent_id = NULL` and `value_ar = $row['en']`
(the JSON is generated from the English p3 prototype). `LookupCatalogService::list` sorted only by
favourite → display_order → value_ar, and the FE `groupLookups` re-sorted categories/loose rows by
display_order. So on a GYN visit the 20 English values landed in the loose «أخرى» section under the
8 general categories.

## Changes
BE (`/home/moonui3/moon-erp-be`)
- NEW `Modules/Gyn/app/Support/GynComplaintCatalog.php` — category constants (`cc_cat_gyn`,
  «شكاوى النسا والتوليد» / "Gynaecology & obstetrics", specialty `gyn_ob`), `LABELS_AR` (code → Arabic,
  20 codes, Egyptian register like `GeneralClinicalCatalogSeed`), `ensureCategory()` (create-only;
  existence check via `DB::table` ignores deleted_at → a clinic-deleted/hidden category is not
  resurrected and returns null), `repair()` (existing installs).
  `gyn_schema.json` NOT edited (generated file; `en` is what ComplaintTemplate / fixtures key on).
- `Modules/Gyn/database/seeders/GynFieldPackSeeder.php` — `seedVocab` now: clinic_complaints
  created with Arabic `name_ar` (name_en stays English; a code-less row is also adopted by the Arabic
  name); creates the category (counts in `lookups_created`) and seeds the complaint lookups under it
  with Arabic `value_ar`. `seedLookup` gained optional `?int $parentId` and uses `$row['ar'] ?? $row['en']`
  (other keys unchanged). Still create-only + idempotent (rerun stats = 0).
- NEW migration `Modules/Gyn/database/migrations/2026_11_20_600001_gyn_complaints_category_and_arabic_labels.php`
  — for each company with gyn_ob complaint rows: ensure category; move pack-code rows still at the
  root under it; replace `value_ar` only when it is empty / equals value_en / equals the pack English
  (a clinic rename is kept; a value the clinic moved under another category stays there). Same name_ar
  rule on the pack's `clinic_complaints` rows, but there (no specialty column) `other` is skipped —
  it may be a clinic's own general «Other» the pack adopted by name — and `name_en` must still be the
  pack English. Idempotent, non-fatal (try/catch + log), table guards.
- `Modules/Clinic/app/Services/LookupCatalogService.php::list` — when `$specialty` is given, sort key
  order is: is_active desc → is_favorite desc → **own specialty first** → display_order → value_ar.
  No-specialty requests unchanged. Favourites deliberately stay on top (doctor's explicit choice; the
  FE shows them as their own ★ block).

FE (`/home/moonui3/public_html/moon-erp`, shared picker only)
- `shared/components/clinical-picker/clinical-picker.model.ts` — new exported `catalogOrder(specialty)`
  comparator (rows whose `specialty` === the requested one first, then display_order, then value_ar;
  no specialty = old order — same guard as the BE). `groupLookups` gained an optional 4th arg
  `specialty` and uses it for categories, children and loose rows. Needed: the picker re-sorts and
  would otherwise discard the BE order.
- `shared/components/clinical-picker/clinical-picker-dialog.component.ts` — passes `this.specialty()`
  to `groupLookups`, and `applyOrder` (manage-mode reorder) uses the same comparator so optimistic
  positions match what is displayed.
- Callers checked (read-only): the visit complaint card binds `[specialty]` (`gyn_ob` on GYN, else
  `general`), `gyn-lookup-field` passes `gyn_ob`, history pickers pass `q.clinical_type ?? null`.
  `visit-exam-card` / `visit-card-settings` call `groupLookups` without a specialty → unchanged order
  there (W5-FE-7's files, not touched).

## Dev (moonui3_dev_be)
- `migrate --force` ran `2026_11_20_600001` (DONE). Company 1: category id 2611 created; 20 children
  under it, Arabic value_ar, value_en English; clinic_complaints name_ar Arabic (e.g. dysmenorrhea →
  «آلام الدورة (عسر الطمث)»). `list(1,'complaint',null,'gyn_ob')` → 75 rows, first = the category then
  its 20 values, row 22 = first core row.
- `composer dump-autoload`, `optimize:clear`, chown moonui3 on all BE files, Pint pass.
- FE `npx ng build --base-href /app/` — success (re-run after the final edits). Not deployed (orchestrator).

## Tests
NEW `Modules/Gyn/tests/Feature/W6bfGynComplaintCatalogTest.php` (5 tests, 111 assertions) — PASS (re-run after the final edits):
labels cover every pack code; fresh install → category + 20 Arabic children + Arabic name_ar, rerun
stats all 0; deleted category not resurrected; migration repairs legacy rows, keeps a rename / a
moved value / a renamed name_ar, leaves another company alone, second run is a no-op; specialty-first
ordering incl. favourite on top and no-specialty order unchanged.
Re-ran (changed code paths): G1aGynFoundationTest 15 ✓, LookupCatalogTest 6 ✓,
W1bCatalogManagementTest 18 ✓, G2aGynCoreModulesTest 12 ✓, GfixGynCleanupTest 8 ✓.

## Not verified / notes
- Not checked visually in the live picker (no deploy) or via the HTTP endpoint with a token; the
  service output on dev and the FE build are verified.
- Dev: clinic_complaints id 32 (`other`) already shows «أخرى (نسا)» from the first migration run,
  before the `other` skip was added; it was pack-created (same created_at as ids 13–31), so the label
  is correct and was left.
- Dental / nutrition: no specialty `complaint` rows exist on dev and neither pack seeds any — nothing to do.
- FE complaint card (W5-FE-7's file, not touched) creates the clinic_complaints row with
  `create(row.value_ar)`; `ComplaintController::store` dedupes on name_en, so picking an Arabic GYN value
  now creates a new code-less clinic_complaints row named in Arabic instead of reusing the coded pack
  row. Harmless for behaviour: GynShowIfContext::complaintCodes and ComplaintTemplate::complaintLabels
  fall back to the pivot's `lookup_id` → lookup code. Cleaner follow-up (optional): send both
  value_ar/value_en from the card, or let `store` also match name_ar.
- Pre-existing (not changed): `seedLookup`'s existing-code check excludes soft-deleted rows, so a
  clinic-deleted GYN pack value would hit the (company, lookup_key, code) unique index on a pack re-run.
- CHANGELOG: not edited (concurrent writers). Suggested [Unreleased] bullet: «شكاوى النسا والتوليد بقت
  في قسم لوحدها وبالعربي، وبتظهر أول القائمة في زيارة النسا / GYN complaints now have their own Arabic
  category and are listed first on GYN visits».
