تمت الموافقة على المقترح الأساسي (لوحة استلام المندوب). النسخة دي بتعدّل الخطة على الملاحظات الجديدة: تفعيل/إيقاف المسار لكل معمل B2B على حِدة · تعدّد المناديب (many-to-many) · دور Courier جديد · تطبيق مندوب موبايل-فيرست بسكان كاميرا سريع — مع توافق خلفي كامل (المعامل غير المفعّلة تفضل زي ما هي).
لما المعمل الخارجي يعمل طلب من بوابته، الكود بيعمل العيّنات بحالة Collected + collected_at=now() على طول (ExternalLabPortalRequestController::store:204) فتظهر في الاستقبال الداخلي فوراً — من غير تسجيل لخطوة النقل ولا مين استلم العيّنة فعلياً.
المخاطرة: الموظّف ممكن «يستلم» العيّنة في النظام قبل وصولها الفعلي — مفيش سلسلة حفظ (مين شالها/إمتى/الحرارة/المسؤولية وهي بره). جدول lab_sample_custody_logs + CustodyAction.Transferred موجود ومصمّم لده بس بيتسجّل يدوي. (CustodyAction.php:5-27)
المسار مش on/off عام — هو سياسة على مستوى كل معمل خارجي (per-tenant policy). كل معمل ليه إعداده الخاص، ومنفصل تماماً عن الباقي.
DB عمود JSON pickup_config على جدول lab_external_labs (الموجود أصلاً). افتراضياً null = المسار متوقّف = سلوك النهاردة.
| المعمل الخارجي | enabled | mode | portal_ready | temperature | السلوك الناتج |
|---|---|---|---|---|---|
| معمل النور (قريب، ثقة) | false | — | — | — | auto-collect مباشر (زي النهاردة) |
| معمل الشفاء (بعيد، مندوب) | true | full | false | true | مسار مندوب كامل + سلسلة حرارة |
| معمل الحياة (تعاون كامل) | true | full | true | true | المعمل يعلّم «جاهزة» → مندوب → تسليم |
| معمل المستقبل (خفيف) | true | simple | false | false | خطوة استلام واحدة بس (تأكيد المندوب = وصلت) |
✅ التوافق الخلفي مضمون: أي معمل enabled=false (أو null) بيمشي في الكود القديم بالظبط — العيّنات Collected فوراً. مفيش أي تأثير على المعامل الشغّالة دلوقتي.
FE تاب جديد «منظومة الاستلام/المناديب» في صفحة تعديل المعمل الخارجي (External Labs): مفتاح التفعيل + اختيار الـmode + الخيارات + تعيين المناديب (نقطة 2). كله مكان واحد لكل معمل.
المطلوب: المعمل الواحد ممكن يكون ليه أكتر من مندوب، والمندوب الواحد ممكن يخدم أكتر من معمل (Many-to-Many). والسؤال المحوري: مين بيختار إن مندوب معيّن هو اللي هيروح يستلم؟ — الإجابة في «نموذج التوزيع» تحت.
معمل الشفاء عليه 3 مناديب (مناطق/أوقات مختلفة) — أي واحد فيهم يشوف عيّناته ويستلم. أول مَن يستلم يتسجّل عليه في الـcustody.
مندوب «أحمد» مسؤول عن 4 معامل في منطقته — شاشته بتجمّع عيّنات الـ4 معامل، مرتّبة بالمعمل/المسافة.
مندوبين بيفتحوا نفس المعمل: الاستلام بيتعمل بـoptimistic-lock على العيّنة — أول اسكان يكسب، التاني يشوف «اتستلمت بواسطة X».
السؤال «مين بيعيّن المندوب؟» بيتحلّ على مستويين منفصلين — وده اللي إنت وصفته بالظبط:
المعمل الداخلي (الأدمن/المدير) هو اللي بيحدّد قائمة المناديب المؤهّلين لكل معمل خارجي (عبر الـpivot) — مش المعمل الخارجي. ده بيتعمل مرة في إعداد المعمل.
مفيش حد بيخصّص مندوب معيّن لأوردر معيّن. الأوردرات الجاهزة بتظهر لـكل المناديب المؤهّلين للمعمل على شاشاتهم. اللي يروح ويعمل اسكان للأمبولة → هي بتتسجّل عليه (custody عليه + بتتحسب في إحصائياته). الاسكان نفسه = الملكية.
المحاسبة (اتحسبت عليه): من lab_sample_custody_logs.to_user_id — تقرير لكل مندوب: كام عيّنة استلم، من أنهي معمل، إمتى، الحرارة، وكام سلّم. مؤشّر أداء ومسؤولية واضح.
وضع اختياري (Explicit Dispatch): لو معمل عايز توزيع صريح — إعداد "dispatch":"assigned": مدير بيخصّص الـrun لمندوب محدّد وبس هو اللي يشوفه. الافتراضي يفضل "pull" (اسكان = ملكية) — اللي إنت طلبته.
الثابت: العيّنة مابتدخلش worklist الاستقبال إلا لما توصل collected — والاستقبال والكانبان والنتائج مايتلمسوش نهائياً. الفرق كله «قبل» الاستقبال.
lis.samples.pickup (استلام/نقل/تسليم).lis.samples.view (مقيّدة بالمعامل المُعيّنة).LisDataScope الموجود).يعيد استخدام منطق السكان + الـbeep (Web Audio) الموجود في صفحات التجميع/الاستقبال — مش من الصفر.
نموذج مبدئي لشاشة المندوب
pickup_config (JSON) على lab_external_labs.lab_external_lab_couriers (external_lab_id, user_id, is_active, assigned_by/at).SampleStatus: at_external_lab · in_transit + دوال الانتقال.picked_up_at/by على lab_samples للعرض السريع.lis.samples.pickup + preset «courier».lab.pickup_config.enabled → at_external_lab أو Collected (التوافق الخلفي). store:204GET /lis/pickups — عيّنات معامل المندوب (مجمّعة بالمعمل، فلتر بالحالة).POST /lis/samples/{id}/pickup → in_transit + custody.POST /lis/samples/{id}/handover → collected + custody.POST /lis/pickups/scan — سكان سريع: بيسجّل العيّنة على المندوب اللي عامل الاسكان (to_user = المستخدم الحالي) + optimistic-lock ضد التزامن.GET /lis/couriers/me/pickups + تقرير: عدّاد/سجل كل مندوب (اتحسبت عليه) من custody logs.GET/POST/DELETE /lis/external-labs/{id}/couriers + حفظ pickup_config.POST /requests/{id}/ready-for-pickup.lab_sample_custody_logs تلقائياً (مش يدوي).| الحالة | المعالجة |
|---|---|
| معمل مش مفعّل | سلوك النهاردة بالظبط — auto-collect → استقبال (صفر تغيير). |
| عيّنة ضاعت في الطريق | من in_transit → lost (custody + سبب) → recollect. |
| استلام جزئي | الباقي يفضل at_external_lab؛ العدّاد يبيّن المتبقّي. |
| مندوبين متزامنين | optimistic-lock على العيّنة — أول استلام يكسب، التاني يشوف «اتستلمت بواسطة X». |
| المعمل عمل طلب وألغاه | الإلغاء (LIS-120) يفضل شغّال قبل الاستلام (نوسّع الشرط «before pickup»). |
| 0 مندوب ومسار مفعّل | config open_pool: يشوفه أي courier؛ أو تنبيه للأدمن يعيّن مندوب. |
| الطلب الداخلي (المسار ب) | مايتأثرش — Pending→Collected زي ما هو. |
pickup_config + جدول المناديب + الحالتين + الصلاحية + دور courier. فرع portal store (التوافق الخلفي).
pickups/pickup/handover/scan + couriers CRUD + حفظ الإعداد. (B اختياري: ready-for-pickup.)
تاب «منظومة الاستلام» في External Labs: التفعيل + الـmode + الخيارات + اختيار المناديب.
Board بالمعامل + سكان كاميرا + beep/اهتزاز + تاب «في الطريق» + تسليم + عرض سلسلة الحفظ.
lost/partial/concurrency/cancel، صلاحيات الدور، اختبار لكل mode + التوافق الخلفي.
| الجزء | الحجم التقديري |
|---|---|
| Backend (config + pivot + states + endpoints + custody + role) | متوسط–كبير |
| Frontend — إعداد الأدمن + تعيين المناديب | متوسط |
| Frontend — تطبيق المندوب موبايل + سكان | متوسط |
| بوّابة B2B (ready-for-pickup، اختياري) | صغير |
🎯 الخلاصة: منظومة سلسلة حفظ قابلة للتهيئة لكل معمل، بتعدّد مناديب، دور مخصّص، وتطبيق موبايل سريع — مبنية على ~80% بنية موجودة (custody logs/reception/roles/branches/portal)، وبتوافق خلفي كامل يحمي المعامل الشغّالة. جاهز أبدأ التنفيذ بعد موافقتك على القرارات فوق.