# LIS Barcode — serial_scope + monthly + info panel — عقد + ليدجر (2026-07-08)

> الخطة المعتمدة (owner). دراسة: `public_html/barcode-serial-scope-plan.html`. مرجع: `topics/lis-sample-generation.md` + `barcode-number-format`. **Fable 5 مستشار (اتحكّم في التصميم)، Codex يراجع كل WP.**

## القرارات المعتمدة (المالك 2026-07-08)
① الأب في sample-scope = **NULL barcode** (حاوية، بلا فجوة سريال). ② البورتال B2B **يتبع نفس `serial_scope`** (دالة مشتركة `assignTubeBarcode`). ③ **حارس دقّة التاريخ الآن** (شهري⟵شهر، يومي⟵يوم، وإلا downgrade+log). ④ تنبيه الشاشة إن تغيير الوضع يغيّر معنى العدّاد اليومي.

## التصميم (Fable 5)
- مفتاح `serial_scope: 'request'(default) | 'sample'` في `lis.barcode_config` (JSON، صفر migration، strict `=== 'sample'`).
- **sample:** الباركود = `buildBase()` فقط (بلا suffix)؛ كل أنبوبة تسحب `generateBarcode()`. الأب داخلي = NULL (يتحوّل لسريال في catch لو aliquot فشل ووقف أنبوبة وحيدة). aliquot children تسحب base فريش. `sample_number` يفضل بـ`-NN` index.
- **request:** السلوك الحالي (base مشترك + suffix).
- **دالة مشتركة `assignTubeBarcode(cfg, mode, base, builder, ctx, scope)`** تُستخرج وتُستخدم من generateForRequest + B2B controller (منع الدريفت — درس «٧ مسارات اتفرّقت»).
- **transaction wrap** لجسم توليد أنابيب generateForRequest الداخلي (CreateLabRequest مش ملفوف) عشان السريالات متتالية؛ B2B ملفوف أصلًا.
- **monthly:** arm في `barcodeResetFrequency` (`ResetFrequency::Monthly` جاهز في enum + SequenceService) + `barcodeHasDateSegment` يبقى granularity-aware (يفحص format token: m/n/M/F للشهري، d/j لليومي؛ وإلا downgrade→yearly+log).
- طول السريل: موجود (`width`) — بلا تغيير.
- out of scope: referral + LabExternalSampleService (روتينج/split labels).

## 📊 ليدجر
| WP | المحتوى | يعتمد | مراجعة | الحالة |
|----|---------|-------|--------|--------|
| WP1 | BE core: `serialScope()` + استخراج `assignTubeBarcode()` + تفريع `generateForRequest` (per-tube + parent NULL + DB::transaction wrap) + تفريع `aliquot()`. SampleBarcodeBuilder بلا تغيير. Pest أساسي. | — | Codex + Fable | ✅ |
| WP1b | B2B: `ExternalLabPortalRequestController::store` foreach (~:312) ينادي `assignTubeBarcode` (base مرة قبل اللوب). **ماتلمسش `barcodes()` ~:1135 (شغل المالك غير المتكوميت).** | WP1 | Codex | ✅ |
| WP2 | null-safety sweep: قُرّاء باركود الأب (resources/عرض/فواتير) يتحمّلوا NULL (fallback sample_number). | WP1 | Codex | ✅ |
| WP3 | monthly arm + granularity-aware `barcodeHasDateSegment`. Pest (شهري يعبر حد الشهر + downgrade بلا مقطع شهر). | WP1 | Codex | ✅ |
| WP4 | FE: `serial_scope` selector (يخفي المميّز لما sample) + `monthly` في القائمة + تنبيهات (④ + دقّة التاريخ) + معاينة حية. | WP1 | Codex | ✅ |
| WP5 | FE: لوحة info (أيقونة i) بشرح ثنائي اللغة لكل أوبشن في تاب الباركود. | — | single | ✅ |
| WP6 | Pest شامل + CHANGELOG ثنائي اللغة + نشر /app + push. | الكل | single | ✅ |

**DAG:** BE سلسلة {WP1→WP1b→WP2→WP3} (كلهم LabSampleService/B2B — يُسلسلوا) · FE WP4/WP5 بعد WP1 (يلمسوا lab-info — سلسل أو حذر). ⚠️ صفر migration. ⚠️ phpunit=sqlite.


## ✅ SHIPPED 2026-07-08
كل الـWP مكوميتة+مُراجعة (Codex+Fable5). منشور /app `main-4PWELKFX.js` + hazemdev (BE `aee1a81ee`/FE `ee53b8b`) + CHANGELOG. السويت الكامل LIS: 1063 نجاح/17 pre-existing (LabRequestApi GROUP-BY-HAVING SQLite + stale-0-samples + Dashboard/Payment/ReportPdf/ResultApi/Kanban/CatalogReplace — صفر علاقة بالباركود، مؤكّد بأسباب الفشل). ⛔ المالك: merge→main + ship (MoonStack). ملف الدراسة: `public_html/barcode-serial-scope-plan.html`.
