request_id، فالطلب الواحد يقدر يكون فيه عدة محاولات جمع.
lab_rejection_reasons موجود بالفعل فيه: code, name_ar, name_en, category, is_active, sort_order.category field يقدر يفرّق بين أسباب التأجيل والرفض بدون جدول جديد.LabSample فيه الحقول جاهزة: is_deferred (bool), deferred_reason, deferred_investigation_ids (array), rejected_at, rejected_by, rejection_reason.SampleStatus enum فيه Rejected case.POST /samples/{id}/deferPOST /requests/{id}/recollect يقبل investigation_idsis_deferred = truestatus = pending (مش collected)deferred_reason = <reason_code>deferred_investigation_ids = [...] (التحاليل المؤجلة)barcode = <serial جديد>status = rejected مع rejected_at + rejected_by + rejection_reason.┌────────────────────────────────────────────────────────────┐ │ Collection Worklist [Active 8] [Deferred 3] [Rejected 1]│ ← tabs ├────────────────────────────────────────────────────────────┤ │ │ │ Active tab: (السلوك الحالي بدون تغيير) │ │ Deferred tab: جدول بكل المؤجلات │ │ - barcode + تحاليل + سبب التأجيل + تاريخ + Recollect btn │ │ Rejected tab: جدول بكل المرفوضات │ │ - barcode + سبب الرفض + تاريخ + Recollect btn │ └────────────────────────────────────────────────────────────┘
قبل: ┌─ Blood (Serum) ──────────────────────────────────┐ │ [📋 Collect] [🚫 Reject] [🏥 Refer] │ └───────────────────────────────────────────────────┘ بعد: ┌─ Blood (Serum) ──────────────────────────────────┐ │ [📋 Collect] [🕐 Defer] [🏥 Refer] │ │ ↑ │ │ زر جديد (بديل الـ Reject) │ └───────────────────────────────────────────────────┘ (الـ Reject الموجود حالياً في التجميع يتشال لأنه فعلياً غير منطقي — العينة لسه ما اتجمعتش)
قبل (الاستلام): ┌─ Scan & Receive ─────────────────────────────────┐ │ [✓ Accept] [✗ Reject (textarea للسبب)] │ └───────────────────────────────────────────────────┘ بعد: ┌─ Scan & Receive ─────────────────────────────────┐ │ [✓ Accept] [✗ Reject (dropdown من الأسباب)] │ └───────────────────────────────────────────────────┘ (الزر نفسه ما يتغير — بس الـ dialog يستخدم dropdown بدل textarea حرة)
┌─ Lab Requests ─────────────────────────────────────────────────┐ │ # | Patient | Status | Indicators │ │ R-001 | Ahmed Ali | pending | 🕐(2) 🖨️ ⚠️(1) │ ← icons │ R-002 | Sara Mahmd | in_progress| │ └────────────────────────────────────────────────────────────────┘ 🕐 = has deferred samples (count badge) → opens Recollect Dialog 🖨️ = print deferred barcodes only (visible only when 🕐 > 0) ⚠️ = has rejected samples (count badge) → opens Recollect Dialog
┌─ Recollect Deferred Investigations ─────────────────┐ │ Patient: Ahmed Ali | Request: R-001 │ │ ─────────────────────────────────────────────────── │ │ ☑ Select All │ │ │ │ ☑ Glucose (deferred: Patient not fasting) │ │ ☑ HbA1c (deferred: Patient not fasting) │ │ ☐ Cholesterol (deferred: Awaiting medication) │ │ │ │ Patient appointment: [date+time] (optional) │ │ ─────────────────────────────────────────────────── │ │ [Cancel] [Recollect Selected (2)] │ └──────────────────────────────────────────────────────┘ (الشكل نفسه للمرفوضات مع تغيير العنوان فقط)
┌─ Sample Reasons ──────────────────────────────────────────┐ │ [Deferral Reasons] [Rejection Reasons] │ ← tabs ├───────────────────────────────────────────────────────────┤ │ + New Reason │ │ │ │ ┌──────┬─────────────────────┬─────┬────────┐ │ │ │ Code │ Name (English) │Sort │ Active │ │ │ ├──────┼─────────────────────┼─────┼────────┤ │ │ │ DEF1 │ Patient not fasting │ 1 │ ✓ │ │ │ │ DEF2 │ Awaiting medication │ 2 │ ✓ │ │ │ │ DEF3 │ Patient ill │ 3 │ ✓ │ │ │ └──────┴─────────────────────┴─────┴────────┘ │ └───────────────────────────────────────────────────────────┘
لا نحتاج جدول جديد! جدول lab_rejection_reasons موجود فيه category field. هنستخدمه:
category = 'rejection' → سبب رفضcategory = 'deferral' → سبب تأجيلمهاجرة بسيطة لو محتاج: نضيف category default value أو نتأكد من إنه nullable.
POST /api/lis/samples/{id}/defer
Body: {
reason_code: string, // من lab_rejection_reasons (category=deferral)
notes?: string,
investigation_ids: int[] // التحاليل المراد تأجيلها
}
Behavior:
- لو الـ sample الأصلي عنده تحاليل كثيرة وبس بعضهم اتأجل:
· ينشئ sample جديد بـ is_deferred=true يحوي التحاليل المؤجلة فقط
· الـ sample الأصلي يفضل بتحاليله المتبقية (إن وجدت)
- لو كل التحاليل اتأجلت: الـ sample الأصلي يتحوّل لـ is_deferred=true بدلاً من جديد
// نسخة per-request (للـ icon في صفحة الطلبات)
POST /api/lis/requests/{requestId}/recollect
Body: {
source: 'deferred' | 'rejected',
investigation_ids: int[] // التحاليل المراد إعادة جمعها (لازم على الأقل واحد)
}
Behavior:
- ينشئ sample جديد واحد للـ request نفسه بكل التحاليل المختارة
- barcode جديد، status=pending، collected_at=null، is_deferred=false
- التحاليل المختارة "تتحرك" من العينات المؤجلة/المرفوضة لعينة جديدة جاهزة للجمع
- باقي التحاليل في العينات المؤجلة/المرفوضة تفضل كما هي (لو ما اخترتش الكل)
- التواريخ القديمة محفوظة (audit) — الـ samples الأصلية مش بتتمسح
GET /api/lis/samples?is_deferred=true&status=pending
GET /api/lis/samples?status=rejected
GET /api/lis/requests?has_deferred=true
GET /api/lis/requests?has_rejected=true
الـ LabRejectionReasonController موجود غالباً (لأن الـ rejection شغّال). محتاج نضيف category filter في الـ index:
GET /api/lis/rejection-reasons?category=deferral
GET /api/lis/rejection-reasons?category=rejection
POST /api/lis/rejection-reasons (body فيه category)
PUT /api/lis/rejection-reasons/{id}
DELETE /api/lis/rejection-reasons/{id}
// LabRequestResource: إضافة counters
'deferred_samples_count' => $this->samples()->where('is_deferred', true)->where('status', 'pending')->count(),
'rejected_samples_count' => $this->samples()->where('status', 'rejected')->count(),
المسار: /lab/sample-reasons
category يتعين تلقائياً حسب الـ tab المختار/lis/rejection-reasons?category=deferral + textarea ملاحظات + checkbox-list للتحاليل (لو الـ group فيه أكتر من واحد ومش عاوز يأجلهم كلهم)POST /samples/{id}/defercategory=rejection فقطPOST /samples/{id}/recollect → refreshdeferred_samples_count > 0 مع badge العدد → الضغط يفتح Recollect Dialog (مش navigate)rejected_samples_count > 0 مع badge العدد → الضغط يفتح Recollect Dialogquery params عند الـ initrequest=X: نختار الطلب تلقائياًtab=deferred|rejected: نفتح التب المطلوبmode: 'deferred' | 'rejected'POST /requests/{id}/recollectprintDeferredBarcodes(request) في requests componentGET /samples?lab_request_id=X&is_deferred=true)LisBarcodeLabeService.printLabels() الموجود بالبيانات المناسبةsrc/app/core/services/lis-sample.service.ts
defer(id, payload)recollect(id, payload)listDeferred() و listRejected() (filter helpers)| الترتيب | التاسك | الجانب | الأولوية | التقدير |
|---|---|---|---|---|
| B-1 | تأكيد بنية lab_rejection_reasons + seed أسباب deferral الأساسية | باك | متوسط | ٢٠ د |
| B-2 | POST /samples/{id}/defer endpoint + service logic | باك | حرج | ٣٠ د |
| B-3 | POST /samples/{id}/recollect endpoint + service logic | باك | حرج | ٤٠ د |
| B-4 | تحديث LabRequestResource بالعدادات + filter في samples list | باك | عالٍ | ٢٠ د |
| B-5 | تحديث LabRejectionReasonController بالـ category filter + CRUD | باك | عالٍ | ٢٠ د |
| B-6 | i18n للـ messages + validation rules | باك | متوسط | ١٠ د |
| F-1 | شاشة إدارة الأسباب الجديدة (component + service + model + routes) | فرونت | عالٍ | ٤٥ د |
| F-2 | في التجميع: إزالة Reject + إضافة Defer (زر + dialog + dropdown أسباب) | فرونت | حرج | ٣٠ د |
| F-3 | تبات Deferred/Rejected/Active في شاشة التجميع | فرونت | حرج | ٤٠ د |
| F-4 | أيقونات الإشعار في صفحة الطلبات | فرونت | عالٍ | ٢٥ د |
| F-5 | deep-link query params في شاشة التجميع | فرونت | متوسط | ١٥ د |
| F-6 | Recollect Dialog مشترك (مؤجل/مرفوض) — checkboxes + select all + count | فرونت | عالٍ | ٢٥ د |
| F-7 | تحديث LisSampleService بـ defer + recollect + list helpers | فرونت | عالٍ | ١٥ د |
| F-8 | i18n keys (en + ar) لكل النصوص الجديدة | فرونت | متوسط | ١٥ د |
| F-9 | طباعة بارود العينات المؤجلة من صفحة الطلبات (button + print logic) | فرونت | متوسط | ١٥ د |
| F-10 | في الاستلام: تأكيد dropdown أسباب الرفض (مع filter category=rejection) | فرونت | متوسط | ١٠ د |
الإجمالي: ~٦-٧ ساعات شغل.
Moon ERP — LIS Module | Defer & Reject Workflow Plan | جاهز للمراجعة