تدقيق عرضي في كل الأنظمة (POS · Inventory · Core · Sales · Clinic · LIS · NPHIES · EInvoicing · WebStore) عبر تلات مسارات متوازية: الدوا كمخزون · الدوا كرعاية · الشاشة نفسها. الإجابة المختصرة: أيوه — الشاشة تصلح كأساس ومش محتاجة شاشة تانية، لكن اللي تحت الشاشة مش جاهز: دفتر التشغيلات دلوقتي دفتر جانبي مش نظام مخزون، والصلاحية لا ترفض أي حاجة، والصرف مش مستند أصلًا.
plans/purchases-inventory-flexible-flow.html وtopics/purchases-controlled-flow.md
(قسم DEFERRED/BACKLOG — Phase 4) بيوصفوا كـ«مؤجَّل» حاجات شحنت خلاص:
inventory_lot_balances · inventory_issue_lot_allocations · تخصيص FEFO ·
بُعد الملكية · سياسة اللوط المنتهي · الاختيار اليدوي للّوط · الحفاظ على اللوط في التحويلات — كلها موجودة ومعاها اختبارات.
وبالتحديد السطر purchases-controlled-flow.md:14 («الصلاحية على الأرصدة = رؤية، مش FEFO») بقى غلط.
لكن التصحيح بيقطع في الاتجاهين: المرحلة ٤ بنت دفتر لوطات، ما بنتش نظام مخزون قائم على اللوط. والفرق ده هو كل الإجابة اللي تحت.
topics/pos-overhaul.md اتراجع كمان ومافيهوش انحراف — كل اللي فيه مطابق للكود.
طلبك: «بص على نقاط البيع وحلّل — ينفع تكون واجهة نقاط بيع صيدلية وإيه اللي ناقصه، بعد ما تعمل بحث وتدقيق في كل الأنظمة بشكل عام».
الطلب ده بيتفكّ لتلات أسئلة مختلفة تمامًا، وكل واحد له إجابة مختلفة:
| السؤال | الإجابة | ليه |
|---|---|---|
| هل الشاشة نفسها تصلح؟ | أيوه — من غير شاشة تانية | حلقة الكاشير (باركود أولًا · مفاتيح F · لوحة الباقي · طابور أوفلاين بمفتاح idempotency · جرد أعمى عند الإقفال) صح خلاص، وفيها طبقة حوكمة سعر بتفرض سعر ثابت غير قابل للتعديل — دي بالظبط قاعدة السعر الجبري بإسم تاني. |
| هل اللي تحت الشاشة يصلح؟ | لأ — لسه | الصيدلية مخزونها هو التشغيلة. هنا التشغيلة «تقرير بيطلع صح غالبًا»: ٤ من ٦ منتجات في قاعدة بيانات التطوير خارجة عن التطابق فعلًا. |
| هل نقدر نصرف روشتة؟ | لأ — مفيش حدث صرف أصلًا | الروشتة سجل إكلينيكي بلا كمية وبلا ربط بأي مستند بيع أو مخزون. وPrescriptionStatus::Dispensed متعلَّم عليه في الكود: «محجوز؛ غير قابل للوصول في v1». |
الغلاف بيعرض تخطيط واحد لا غير — pos-layout.component.html:38 فيه @if (posLayout() === 'cashier') وبس.
مجلدات cart-panel/ وproduct-grid/ كود ميت (مش متسمّيين في أي قالب).
فالشاشة = pos-header + invoice-table (يمين) + عمود تحكم ثابت (شمال) + pos-footer.
الحساب: 668px ارتفاع متاح، والكتل الثابتة (كلها flex-shrink:0 بقرار صريح في
pos-layout.component.scss:116-136) بتاخد ≈340px، فيفضل ≈328px للوحة الأرقام
اللي ارتفاعها الطبيعي 392px — يعني هي أصلًا بتـ scroll جوه نفسها.
النتيجة العملية: أي إضافة في عمود التحكم بتتاخد من منطقة مضغوطة أصلًا.
| القدرة | الحالة | الدليل |
|---|---|---|
inventory_lot_balances (منتج·متغيّر·مخزن·مالك·تشغيلة·صلاحية → متبقّي/تكلفة) |
موجود وموصول | Modules/Inventory/database/migrations/2026_07_11_400000_…:43-86 |
| كتابة اللوط عند اعتماد الاستلام | موجود وموصول | Inventory/app/Services/LotBalanceService.php:27-57 |
| البيعة بتخصم من دفتر اللوطات (منتجات batch فقط) | موجود وموصول | Sales/app/Actions/PostSalesInvoice.php:330 → StockService.php:245-259 |
| التخصيص FEFO بالصلاحية تصاعديًا (والـnull آخرًا)، قابل للتحويل لـFIFO | موجود وموصول | Inventory/app/Services/LotAllocationService.php:154-158, :236-241 |
| عكس اللوط عند إلغاء فاتورة | موجود وموصول | Sales/app/Actions/CancelSalesInvoice.php:175 |
| تقرير «التشغيلات القاربة على الانتهاء» + تبويب في الواجهة | موجود وموصول | Inventory/app/Http/Controllers/InventoryReportController.php:1122+ |
| وحدات البيع المتعددة: عامل تحويل ١٥,٦ + باركود لكل وحدة + سعر بيع لكل وحدة | موجود وموصول | Core/…/create_product_units_table.php:11-25 |
| تحويل الكمية للوحدة الأساسية قبل خصم المخزون (صحيح تمامًا) | موجود وموصول | Core/app/Services/UnitConversionService.php:42-107 |
| أرضية السعر بتتدرّج مع الوحدة (علبة ١٠ تتقارن بـ١٠× الأرضية) | موجود وموصول | Sales/…/Concerns/ValidatesMinimumSalePrice.php:143-150 |
باركود لكل وحدة → منتج + وحدة + سعر (matched_unit) | موجود وموصول | POS/app/Http/Controllers/POSProductController.php:131-151 |
| القدرة | الحالة | الدليل / ليه معزول |
|---|---|---|
كيان روشتة حقيقي (prescriptions + prescription_items: جرعة، تكرار، مدة، طريق إعطاء بـenum ١٠ حالات، طبيب، مريض، ترقيم تسلسلي) |
موجود · مش موصول | مسار الكتابة الوحيد IssuePrescription::handle(Encounter,…) — مربوط بالزيارة. المسار POST clinic/encounters/{encounter}/prescriptions. مفيش حاجة برّه Clinic تقدر تقراها أو تكتبها. |
lab_patients.partner_id → business_partners — الجسر بالظبط اللي الـPOS محتاجه |
موجود · مش موصول | LIS/…/create_lab_patients_table.php:27 · فحص القاعدة: 11,797 مريض، 41 بس عندهم partner_id |
| نموذج جهة دافعة ناضج بتقسيم حقيقي (نسبة تحمّل، رقم موافقة، فاتورة مريض + فاتورة تأمين مترابطتين) | موجود · مش موصول | LIS/app/Services/InsuranceInvoiceService.php:224-367 · Clinic/app/Models/Claim.php |
| NPHIES اتصال HTTP حقيقي (mTLS): أهلية، موافقة مسبقة، تقديم مطالبة | موجود · مش موصول | مدخل POST nphies/eligibility بيقبل patient_id → LabPatient. الـPOS ما عندوش patient_id يبعته. |
| تاريخ الصرف الإكلينيكي مُجمَّع بالفعل مع أسماء الأدوية | موجود · مش موصول | Clinic/app/Services/VisitHistoryService.php:44-52 (بيحمّل prescriptions.items.drug) |
| رقم ترخيص مهني | موجود · مش موصول | lab_doctors.license_number — جدول إحالة/عمولة أطباء، مش سجل اعتماد موظفين. users ما فيهاش ترخيص. |
product_serials بـbatch_number/expiry_date/حالة |
موجود · مش موصول | بتتكتب بس من ApproveReceipt/ApproveIssue. والقرار المقفول رقم ٣ في الـPOS إن البيعة ما بتعملش إذن صرف ⇒ السيريال ما بيتحركش أبدًا في بيعة POS. |
الفوترة الإلكترونية موصولة بمسار بيعة POS — وZATCA بيصنّف بيعة الكاش صح كفاتورة مبسّطة 0200000، وفيه مولّد QR |
موجود · مشروط | EInvoicing/…/ZatcaXmlBuilder.php:99 · ZatcaQrGenerator.php:23 تصحيح (2026-08-01): كنت كتبت هنا «الـQR متولّد ومتخزّن خلاص» — ودي مضلّلة. einvoicing.enabled مقفول افتراضيًا ومفيش شركة مفعّلاه هنا ⇒ ما بيتعملش مستند ⇒ مفيش QR. وكمان التوقيع نفسه stub (بيتحقن كتعليق XML مش UBLExtensions/XMLDSIG)، وأعمدة العنوان الوطني السعودي (رقم المبنى، الحي، القطعة، المنطقة) والاسم القانوني العربي مش موجودين/مش بيتطبعوا. ⇒ الإيصال السعودي المطابق متوقّف على الـschema مش على القالب، وWP24 مش شبه مجاني زي ما قدّرت. |
مش قايمة رغبات — دي القدرات اللي من غيرها الشاشة مش صيدلية:
موجود وموصول = شغّال دلوقتي · موجود · مش موصول = مبني بس الـPOS ما يوصلوش (ده أرخص مكسب) · مش موجود = محتاج بناء.
| المطلوب | الحالة | الموجود فعلًا | اللي لازم يتغيّر |
|---|---|---|---|
| ١. التشغيلة والصلاحية على السطر | مش موجود عند العدّاد | الدفتر موجود وFEFO شغّالة على السيرفر. لكن POSProductResource ما بيرجّعش أي lot/batch/expiry — وtracking_type نفسه متشال منه (موجود في ProductResource العام). |
إضافة الحقول للـResource + عرضها في خانة .product-meta الموجودة أصلًا في سطر السلة. |
| ٢. منع بيع المنتهي | مش موجود حرج | inventory.expired_issue_policy افتراضيًا block — لكن قراءته الوحيدة في الريبو هي جملة SELECT واحدة في LotAllocationService.php:249. |
block «تصفية» مش «رفض»: بيستبعد اللوط المنتهي من FEFO، والكمية غير المغطّاة بترجع shortfall، وdecreaseStock بيرميها. المخزون اتخصم خلاص. ⇒ الدوا المنتهي بيتباع عادي. |
| ٣. وحدة الصرف (علبة/شريط/قرص) | موجود · مش موصول | كل الحسابات صح: تحويل، تسعير، أرضية سعر متدرّجة، دمج أسطر منفصل لكل وحدة. | مفيش أي مُبدِّل وحدة في أي قالب POS (بحث في كل الـHTML = صفر نتيجة). وindex()/search() بيحمّلوا ['category','baseUnit'] بس ⇒ منتج جاي من البحث ما معهوش قايمة وحدات أصلًا. + عيب: سطر السلة بيعرض base_unit دايمًا حتى لو الوحدة كرتونة. |
| ٤. السعر الجبري | نصف موجود | طبقة حوكمة السعر شغّالة وبتقول للكاشير ليه السعر مقفول — دي أقوى ملاءمة صيدلية في الشاشة كلها. | الموجود أرضية، والمطلوب مساواة. مافيش max ولا علم «سعر مقفول»، وPriceType ما فيهوش حالة سعر حكومي. + allow_price_override مش مُطبَّق على السيرفر إطلاقًا (صفر قراءة في أي controller). |
| ٥. المريض بدل الزبون | موجود · مش موصول أرخص مكسب | الجسر lab_patients.partner_id موجود، وLabPatientWriteService بينشئ الشريك تلقائيًا. ومنتقي الزبون في الـPOS فيه بحث بعيد + إنشاء فوري. |
مصدر بحث تاني في المنتقي بيرجّع partner_id. العقد ما بيتغيّرش — الـPOS يفضل يبعت customer_id. + تعبئة رجعية لـ11,756 مريض. |
| ٦. الروشتة والصرف | مش موجود أكبر عائق | الروشتة كسجل إكلينيكي كاملة ومختبَرة. | مفيش كمية على سطر الروشتة ⇒ مفيش حاجة تتصرف. مفيش dispensed_quantity، مفيش تكرار صرف، مفيش صلاحية للروشتة، ومفيش مفتاح أجنبي في أي اتجاه بينها وبين أي مستند بيع. |
| ٧. التأمين على العدّاد | مش موجود على مسار Sales/POS | نموذج كامل في LIS/Clinic، وNPHIES حقيقي. | sales_invoices مافيهاش أي عمود تأمين/دافع/موافقة، وPOSPaymentMethod مافيهوش حالة تأمين. بحث «insurance/payer» في Modules/Sales + Modules/POS = صفر. |
| ٨. المخدِّرات والمقيَّدة | مش موجود — ولا حاجة | — | مفيش علم is_controlled/requires_prescription، مفيش تصنيف جدول، مفيش سجل، مفيش تقرير رقابي. بحث في الريبو كله = صفر. |
| ٩. هوية الصيدلي | مش موجود | البيعة بتسجّل created_by وapproved_by. |
مفيش «صرف بمعرفة» منفصل، مفيش ترخيص على users، ومفيش صلاحية pharmacy.*. وملحوظة أمنية: مدخل بيعة الـPOS محروس بـsales.invoices.create — أي حد يقدر يعمل فاتورة مكتبية يقدر يرحّل بيعة على الماكينة. |
| ١٠. البديل الجنيسي | نصف موجود | منتقي المتغيّرات موجود بالكامل (قائمة خيارات بسعر ومخزون + مفتاح Escape) — بس ميت لأن POSProductResource ما بيرجّعش has_variants أبدًا. |
يتحوّل لمنتقي بدائل: «نفس المادة الفعّالة — ٤ بدائل». محتاج عمود اسم علمي ما هوش موجود على products (موجود على clinic_drugs.active_ingredient — جدول تاني). |
| ١١. الإيصال القانوني | موجود · مش مطبوع | الـQR بتاع ZATCA متولّد ومتخزّن على السيرفر خلاص. | بحث في pos-receipt.service.ts عن qr|vat|tax_number|zatca = صفر. ناقص كمان: التشغيلة، الصلاحية، الوحدة المصروفة (normaliseLines بيرمي unit_id تمامًا)، الاسم العلمي، الطبيب، الصيدلي، الرقم الضريبي. |
| ١٢. المرتجع الدوائي | مش موجود مصدر انحراف | المرتجع بيرجّع المخزون فعلًا. | POSRefundController.php:222 بيثبّت 'affects_inventory' => true — مفيش خيار حجر أو إتلاف. والأهم: PostSalesReturn بيرجّع المخزون بلا أي لوط ⇒ الوحدات ترجع بلا تشغيلة وبلا صلاحية وتختفي من FEFO للأبد. |
في جملة واحدة: التوافر بيتقرّر بلا لوط، والصلاحية ما تقدرش ترفض حاجة، وخصم البيعة بينجح دايمًا وخصم اللوط اختياري، ومسار المرتجع ما بيرجّعش لوط أبدًا.
أربع تسريبات مستقلة بتخلّيه ينحرف:
decreaseStock بيخصم stock_balances أولًا وبلا شرط (StockService.php:212)، وبعدين يخصّص لوطات ويرمي الـshortfall (:245-259). بلا لوج، بلا علم، بلا استثناء.expired_issue_policy = block يستبعد بدل ما يرفض.PostSalesReturn بيرجّع بلا لوط، والـPOS بيخلّي الإرجاع بلا شرط.allocate_lots.وده مش نظري — قِسناه على moonui2_dev_be (قراءة فقط):
| المنتج | Σ stock_balances | Σ lot_balances المتبقّي | الفرق |
|---|---|---|---|
| 17159 | 365.000 | 154.000 | −211 |
| 17161 | 30.000 | 30.000 | مطابق |
| 17162 | 7.740 | 5002.740 | +4995 |
| 17163 | 687.000 | 687.000 | مطابق |
| 17164 | 200.000 | 10.000 | −190 |
٤ من ٦ منتجات batch خارجة عن التطابق — في الاتجاهين — والـmigration نفسها بتعلن هذا التطابق كـ«ثابت» في رأسها.
وكمان: ٩ لوطات منتهية لسه رصيدها أكبر من صفر ⇒ غير قابلة للتخصيص للأبد تحت سياسة block،
بينما وحداتها قابلة للبيع بالكامل من خلال stock_balances.
وطبقة تانية: تقرير «التشغيلات القاربة على الانتهاء» لا يعكس بيعة POS أبدًا —
فرعا الـbatch المملوك للشركة بيقروا inventory_receipt_item_batches.quantity اللي بتتكتب مرة واحدة عند الاستلام
وما بتتخصمش من أي مسار صرف. فتشغيلة اتباعت للصفر بتفضل ظاهرة بكميتها الكاملة كـ«قاربة على الانتهاء» للأبد.
الروشتة سجل إكلينيكي، ومفيش حدث صرف من أي نوع تتحوّل ليه.
مش حقل ناقص — النص التاني كله (التنفيذ) غايب: سطر الروشتة ملوش كمية،
مفيش dispensed_quantity، مفيش عدّاد تكرار، مفيش نافذة صلاحية،
ومفيش مفتاح أجنبي في أي اتجاه بين prescriptions وأي مستند بيع أو مخزون.
والتفصيلة الحاملة:
ResolveDrugProduct.php:59 بيحوّل دواء الفورميلاري لمنتج Core بـ'track_inventory' => false
(والتعليق: «الفورميلاري مفهوم إكلينيكي، مش مخزون»).
يعني المنتجات اللي الطبيب بيكتبها هي بالتحديد المنتجات اللي بيعة POS ما تقدرش تخصم مخزونها.
الوصف والبيع دلوقتي بيتحلّوا لمجموعتين منفصلتين من سجل الأصناف.
وكل فجوة تانية في مسار الرعاية معلّقة على الحدث الغايب ده: سجل المخدِّرات هو سجل صرفيات، والتحقق من التفاعلات بيحصل عند الصرف، و«المريض ده أخد إيه قبل كده» استعلام على الصرفيات.
products فيها ٣٩ عمود. لا اسم علمي، لا مادة فعّالة، لا تركيز، لا شكل صيدلاني، لا تصنيف ATC، لا Rx/OTC، لا مخدِّر، لا حرارة تخزين، لا بلد منشأ.
clinic_drugs، جدول تاني، وResolveDrugProduct بيرميهم وهو بيعمل المنتج.manufacturer_id موجود على products — بس بيتقبل من WebStore بس، وغايب عن ProductResource وعن الـFormRequests بتوع Core.shelf_life_days موجود — وغايب عن شاشة المنتجات، بيتكتب بس من الإنتاج.دفتر اللوطات يبقى صادق ← الصلاحية تبقى رفض ← العدّاد يشوف اللوط
↓
المريض على البيعة ← الأهلية/التأمين ← الفاتورة المقسومة
↓
هوية الدواء (اسم علمي) ← البديل الجنيسي
↓
الصرف كمستند ← المخدِّرات + السيريال + تاريخ الصرف
كل سهم اعتماد حقيقي: عرض تشغيلة على شاشة بينما الدفتر منحرف = عرض رقم غلط بثقة. وسجل مخدِّرات بلا حدث صرف = سجل فاضي.
| الحالة | المعالجة المقترحة |
|---|---|
| الكمية موزّعة على أكتر من تشغيلة (١٠ علب: ٦ من B-1، ٤ من B-2) | سطر واحد في السلة، وتخصيص متعدد تحته. الإيصال يطبع التشغيلتين. FEFO بتعمل ده على السيرفر خلاص — الناقص العرض. |
| الكمية أكبر من كل اللوطات غير المنتهية | اليوم: يبيع بصمت ويترك عجز. المقترح: 422 باسم الصنف والمتاح — بانر خطأ البيعة الموجود بيعرض الجملة دي حرفيًا خلاص. |
| منتج batch لكن مستلم بلا تشغيلة | ممكن اليوم (ReceiptLotService.php:45 بيعدّي على السطر). لازم يبقى ممنوع عند الاستلام، مش عند البيع — وإلا نكتشفه على العدّاد قدام العميل. |
| مرتجع لدوا خرج من الصيدلية | قرار المالك (بند ٤ تحت). تقنيًا: مفتاح restock على سطر المرتجع + إعادة اللوط الأصلي أو حجر. |
| مرتجع لتشغيلة انتهت بعد البيع | ترجع للحجر إجباريًا مهما كان الإعداد. |
| أوفلاين + صلاحية | الكاش المحلي بيحمل أقرب صلاحية وقت المزامنة. ما يتقفلش الباب: التحذير يفضل، والرفض النهائي على السيرفر عند المزامنة — نفس منطق idempotency الموجود. |
| أوفلاين + مخدِّر | ما يتصرفش أوفلاين إطلاقًا. سجل مخدِّرات بيتزامن بعدين مش سجل. |
| صرف جزئي لروشتة | «الأوردر المعلَّق» الموجود هو الشكل الطبيعي: المريض ياخد ٢ من ٤ دلوقتي والباقي محجوز على نفس الروشتة. HeldOrder.notes بيدور ذهابًا وإيابًا خلاص. |
| تأمين + كوبون + خصم يدوي معًا | تحمّل الجهة يُحسب بعد الخصومات. ومتروك معروف: خصم السطر لسه يقدر ينزل تحت أرضية السعر — ولازم يتقفل على التلات طلبات معًا (invoices/orders/POS). |
| الوحدة كرتونة والأرضية على القرص | متعالَج صح خلاص — الأرضية بتتدرّج بعامل التحويل. |
| باركود مكرر بين منتجين | حل غير محدَّد اليوم. مفتاح فريد لكل شركة + التحقق من صيغة GTIN (الدالة موجودة في WebStore). |
مريض بلا partner_id |
يتعمل عند الاختيار (LabPatientWriteService بيعمل كده خلاص) + تعبئة رجعية مرة واحدة. |
القاعدة اللي التصميم كله ماشي عليها: عمود التحكم (شمال) ما ياخدش أي حاجة جديدة غير الفلوس.
كل اللي وصفي بيروح للوحة اليمين — عندها ≈872px عرض غير مستغل موزّعة على ستة أعمدة مرنة، وجسم بيـscroll.
/app| # | اسم الصنف | الكمية | السعر | الخصم | الضريبة | الإجمالي | |
|---|---|---|---|---|---|---|---|
| 1 | أوجمنتين 1جم | 2 | 48.00 | — | — | 96.00 | 🗑 |
| 2 | بانادول إكسترا | 5 | 8.90 | — | — | 44.50 | 🗑 |
اللي الصيدلي مش شايفه: مفيش تشغيلة · مفيش صلاحية · مفيش رصيد أصلًا — بيعرف إن الصنف خلص من خطأ ٤٢٢ لحظة الدفع. والوحدة المعروضة هي الأساسية دايمًا حتى لو مسح كرتونة.
| # | اسم الصنف | الكمية | الوحدة | السعر | الخصم | الضريبة | الإجمالي | |
|---|---|---|---|---|---|---|---|---|
| 1 | أوجمنتين 1جم ⟨أموكسيسيلين⟩ | 2 | علبة ▾ | 48.00 🔒 | — | — | 96.00 | 🗑 |
| 2 | بانادول إكسترا ⟨باراسيتامول⟩ | 5 | شريط ▾ | 8.90 🔒 | — | — | 44.50 | 🗑 |
| الإضافة | بتنزل فين | التكلفة |
|---|---|---|
| التشغيلة + الصلاحية | .product-info > .product-meta — موجودة ومنسّقة وبتحمل دلوقتي الوحدة والباركود | صفر عرض · +12px للصف |
| شارة انتهاء / قرب انتهاء | نفس مفردات الشارات .badge-* الموجودة | CSS فقط |
| عمود الوحدة | عمود جديد ~72px من باقي عمود الاسم (العمود الوحيد غير المقيَّد) | عمود واحد |
| مُبدِّل الوحدة | حقل سادس في شريط التعديل (خمسة حقول على ~940px) | فيه مساحة |
| شريط المريض | صف رأس اللوحة (44px → 64px، سطرين) | تعليق/إلغاء/مضغوط تروح لـ«⋯» |
| تقسيم التأمين | شريط المديونية — نفس الشكل ونفس المكان، أرقام مختلفة | مكوّن موجود |
| منتقي البديل الجنيسي | منتقي المتغيّرات الميت — قائمة بسعر ومخزون ومفتاح Escape جاهزين | إعادة توظيف |
| «يدفع المريض» | سطر رابع في .pay-total — الإضافة الوحيدة المقبولة في عمود التحكم لأنها بتغيّر المبلغ المستلَم | 14px من لوحة الأرقام |
| السعر الجبري | قفل حوكمة السعر الموجود — allow_price_override=false + أرضية = السعر المطبوع | إعداد، مش كود UI |
| # | الصنف | الكمية | الوحدة | السعر | الإجمالي | |
|---|---|---|---|---|---|---|
| 1 |
أوجمنتين 1جم ⟨أموكسيسيلين⟩ ← عمود BE جديد
↑ في
.product-meta الموجودة — بدل الوحدة+الباركود |
─ 2 + | علبة ▾ 72px |
48.00 🔒 | 96.00 | 🗑 |
مفيش QR · مفيش رقم ضريبي · مفيش تشغيلة · مفيش صلاحية · مفيش وحدة («2» يعني إيه؟) · مفيش صيدلي
الترتيب مش اقتراح — هو اعتماد. عرض تشغيلة على الشاشة قبل ما الدفتر يبقى صادق = عرض رقم غلط بثقة، وده أسوأ من عدم العرض. المرحلة أ شرط لكل اللي بعدها.
| WP | النطاق | المستودع | Migration | الاختبار اللي يثبته |
|---|---|---|---|---|
| WP1 | التشغيلة + الصلاحية إجباريتين عند اعتماد الاستلام لمنتج tracking_type=batch (دلوقتي ReceiptLotService.php:45 بيعدّي على السطر) | BE | لا | استلام batch بلا تشغيلة ⇒ 422 |
| WP2 | الـshortfall يبقى قاتل مش صامت لمنتجات batch: التخصيص قبل خصم الرصيد، أو استثناء قبل StockService.php:212 | BE | لا | بيع أكتر من اللوطات ⇒ 422 والمخزون ما يتحركش |
| WP3 | إعادة اللوط في المرتجعات: PostSalesReturn يعكس تخصيص الفاتورة أو يقبل لوط صريح + مفتاح restock على سطر مرتجع الـPOS | BE | نعم | مرتجع ⇒ يرجع لنفس اللوط؛ حجر ⇒ ما يرجعش للرصيد المتاح |
| WP4 | توصيل allocate_lots في ApproveAdjustment وCancelReceipt | BE | لا | تسوية سالبة ⇒ اللوط يتخصم |
| WP5 | تقرير + أمر تسوية Σ لوطات مقابل stock_balances — لتصفية الانحراف القائم (٤ من ٦ منتجات) | BE | لا | التقرير يكشف الخمس حالات المقيسة |
| WP | النطاق | المستودع | Migration | الاختبار |
|---|---|---|---|---|
| WP6 | توافر واعٍ بالصلاحية في assertStockAvailable(): المتاح = Σ المتبقّي غير المنتهي، مش stock_balances.quantity + 422 على السيرفر لما block ما تتحققش | BE | لا | لوط منتهي فقط ⇒ البيعة ترفض بالاسم والمتاح |
| WP7 | تطبيق inventory.issues.expired-override على السيرفر (دلوقتي نص مزروع بمعنى واجهة فقط) + فرع صلاحية في pickManual() | BE | لا | بلا الصلاحية ⇒ 403؛ بيها ⇒ يمر مع تعليم expired_override |
| WP8 | توجيه فرعَي الـbatch في تقرير «القاربة على الانتهاء» لـlot_balances.remaining_quantity (الفرع ٤ بيعمل ده للأمانات خلاص — قلب فلتر) | BE | لا | تشغيلة اتباعت للصفر ⇒ ما تظهرش |
| WP | النطاق | المستودع | Migration | الاختبار |
|---|---|---|---|---|
| WP9 | POSProductResource: tracking_type + أقرب صلاحية + المتاح لكل لوط. + إعداد pharmacy_mode على الماكينة (وقايمة إعداداتها الموجودة) | BE | نعم | عقد الحمولة في POSFrontendPayloadContractTest |
| WP10 | عرض التشغيلة والصلاحية في .product-meta + شارة انتهاء/قرب انتهاء + المتاح عند المسح (بدل ما يعرفه من ٤٢٢) | FE | — | ng build + فحص بصري RTL |
| WP11 | اختيار لوط اختياري في الـPOS: إعلان items.*.lot_allocations (StockService بيمرّر manual خلاص) | BE+FE | لا | اختيار يدوي ⇒ يتخصم من نفس اللوط |
| WP | النطاق | المستودع | Migration | الاختبار |
|---|---|---|---|---|
| WP12 | eager-load('productUnits') في index() وsearch() — ده كل شغل الباك-إند | BE | لا | البحث يرجّع قايمة وحدات |
| WP13 | مُبدِّل وحدة في شريط التعديل + setUnit(index, unitId) بإعادة استخدام resolveUnitPrice + إصلاح عرض الوحدة الخطأ على السطر | FE | — | تبديل علبة/شريط ⇒ السعر والأرضية يتبعوا |
| WP14 | الكاش الأوفلاين يطابق باركود الوحدات (دلوقتي p.barcode بس) | FE | — | مسح كرتونة أوفلاين ⇒ يتعرف |
| WP | النطاق | المستودع | Migration | الاختبار |
|---|---|---|---|---|
| WP15 | منتقي الزبون يبحث المرضى ويرجّع partner_id — العقد ما يتغيّرش (الـPOS يفضل يبعت customer_id) | BE+FE | لا | اختيار مريض ⇒ بيعة صحيحة على شريكه |
| WP16 | تعبئة رجعية لـpartner_id (١١٬٧٥٦ مريض) — migration أمامية idempotent | BE | نعم | تشغيل مرتين ⇒ نفس النتيجة |
| WP17 | شريط المريض في رأس اللوحة + لوحة «آخر صرف» من visit-history الموجود | FE | — | ng build |
| WP18 | مفهوم الجهة الدافعة على مسار Sales/POS: تقسيم تحمّل + رقم موافقة + شريط التأمين (شكل شريط المديونية) | BE+FE | نعم | تقسيم = الإجمالي؛ الترحيل صح على الحسابين |
| WP19 | زرار أهلية NPHIES من العدّاد (المدخل موجود، محتاج patient_id بس) | FE | — | استدعاء يرجّع نسبة التحمّل |
| WP | النطاق | المستودع | Migration | الاختبار |
|---|---|---|---|---|
| WP20 | أعمدة دوائية على products: اسم علمي/مادة فعّالة · تركيز · شكل · Rx/OTC · مخدِّر · حرارة تخزين · ATC + كشف manufacturer_id وshelf_life_days في Core | BE | نعم | CRUD + Resource |
| WP21 | البحث بالاسم العلمي (ProductService::search()) + تحويل منتقي المتغيّرات الميت لمنتقي بدائل | BE+FE | لا | بحث «باراسيتامول» ⇒ كل البدائل |
| WP22 | سعر مقفول: علم price_is_fixed + فحص مساواة جنب الأرضية، يغطي خصم السطر والرأس معًا (بيقفل كمان المتروك المعروف) | BE | نعم | أي انحراف ⇒ 422 على التلات طلبات |
| WP23 | تطبيق allow_price_override على السيرفر + مفتاح فريد للباركود لكل شركة + تحقق GTIN | BE | نعم | POST مباشر بسعر معدَّل ⇒ 422 |
| WP24 | إيصال قانوني: QR بتاع ZATCA + الرقم الضريبي + الوحدة (normaliseLines بيرميها) + التشغيلة + الصلاحية + الصيدلي | BE+FE | لا | معاينة الإيصال |
| WP | النطاق | المستودع | Migration | الاختبار |
|---|---|---|---|---|
| WP25 | إصلاح ResolveDrugProduct: يحافظ على المادة الفعّالة والشكل والتصنيف، ويطابق على code/sku مش name، وtrack_inventory يبقى قرار مش ثابت | BE | لا | دواء فورميلاري ⇒ منتج قابل للتخزين |
| WP26 | كمية على سطر الروشتة + dispensed_quantity + صلاحية الروشتة + تكرار الصرف | BE | نعم | صرف جزئي ⇒ الباقي صحيح |
| WP27 | حدث الصرف: ربط بين الروشتة والفاتورة، وتفعيل PrescriptionStatus::Dispensed | BE | نعم | صرف كامل ⇒ الحالة تتغيّر؛ جزئي ⇒ لأ |
| WP28 | الصرف الجزئي على الأوردر المعلَّق (F4) بمرجع الروشتة | FE | — | استرجاع ⇒ الباقي بس |
| WP | النطاق | المستودع | Migration | الاختبار |
|---|---|---|---|---|
| WP29 | سجل المخدِّرات: قيد متسلسل لكل صرف + الطبيب + المريض + منع الصرف أوفلاين + منع المرتجع | BE+FE | نعم | مخدِّر أوفلاين ⇒ يترفض؛ التسلسل بلا فجوات |
| WP30 | هوية الصيدلي: ترخيص على المستخدم + «صرف بمعرفة» على البيعة + صلاحية pos.sales.create منفصلة بدل sales.invoices.create | BE | نعم | صلاحية مكتبية وحدها ⇒ ما تقدرش ترحّل على الماكينة |
| WP31 | سيريال عند البيع (اليوم ما بيتحركش أبدًا في بيعة POS) + قراءة GS1 DataMatrix | BE+FE | لا | بيع سيريال ⇒ حالته تتغيّر لمباع |
توصيتي: علم pharmacy_mode على الماكينة — مش فرع.
حلقة العدّاد صح خلاص وكلّفت مراجعة ١٢ حزمة (RejectsUnknownKeys، سباق local_id، سقف مرتجع الدرج).
شاشة تانية معناها إعادة كسب الصح ده من الأول بلا مقابل.
وقايمة إعدادات الماكينة الحالية (allow_discount، require_customer…) هي بالظبط المكان الطبيعي للمفاتيح الجديدة.
ده بيحدّد تلات حاجات ما ينفعش نخمّنها: السعر الجبري (مساواة ولا سقف)،
التتبّع الحكومي (RSD السعودي مقابل T&T المصري)، ومحتوى الإيصال القانوني.
لو السعودية أولًا: مسار ZATCA متتبَّع بالكامل والـQR جاهز على السيرفر — WP24 يبقى شبه مجاني.
لو مصر، محتاج نقرا متطلبات EtaDocumentBuilder الأول.
لو تجزئة مستقلة: المرحلة ز (الصرف كمستند — ٤ حزم) تتأجّل بالكامل، والصيدلية تشتغل ببيع عادي بتشغيلة وصلاحية.
لو مربوطة بالعيادة: المرحلة ز شرط، وWP25 (ResolveDrugProduct) لازم يسبق كل حاجة لأن الأدوية الموصوفة حاليًا غير قابلة للتخزين أصلًا.
توصيتي: ابدأ تجزئة مستقلة — بتوصل ٨٠٪ من القيمة بـ٢٠٪ من الشغل، والصرف يتبني بعدين على أساس صادق.
اليوم بيرجع دايمًا وبلا شرط، وبلا تشغيلة — ودي أكبر مصادر الانحراف المقيس. توصيتي: إعداد على الشركة، الافتراضي «حجر» — أغلب اللوائح بتمنع إعادة بيع دوا خرج من الصيدلية. الحجر برضه بيحتاج WP3 عشان نعرف اللوط اللي رجع.
٤ من ٦ منتجات batch في التطوير خارجة عن التطابق، و٩ لوطات منتهية برصيد موجب. توصيتي: WP5 يطلع تقرير أولًا (بلا كتابة) — نشوف الحجم على بيانات العميل الحقيقية، وبعدين تقرّر التسوية. التسوية العمياء على أرقام ما شفناهاش أخطر من الانحراف نفسه.
مفيش منها ولا حاجة النهارده — ولا علم ولا سجل ولا تقرير.
توصيتي: برّه المرحلة الأولى، بس علم is_controlled يتضاف في WP20 مع باقي الأعمدة
عشان ما نعملش migration تاني على products بعدين. السجل نفسه (WP29) يستنى قرار البلد.
مدخل بيعة الـPOS محروس بـsales.invoices.create — أي حد يقدر يعمل فاتورة مكتبية يقدر يرحّل بيعة على الماكينة،
وallow_price_override مش مطبَّق على السيرفر إطلاقًا.
دول مش خاصين بالصيدلية — مفتوحين دلوقتي على نقاط البيع العادية.
توصيتي: نقلهم لباتش مستقل صغير قبل أي شغل صيدلية.
| البند | الحالة |
|---|---|
| سبب كل صف انحراف على حدة | قِسنا الفروق (17162 بـ+4995، 17164 بـ−190) وحدّدنا أربع آليات تقدر تنتجها، بس ما تتبعناش تاريخ الحركات لكل منتج عشان ننسب كل فرق لآليته. تتبّع inventory_movements بالقراءة يحسمه. |
| هل مسار POS→لوط اشتغل على بيانات حقيقية؟ | موصول في الكود ومغطّى باختبارات، بس في التطوير: ١١ فاتورة POS وصفر تخصيص لوط. يعني مش مُجرَّب، مش مثبَت خطأه. |
| هل NPHIES شغّال عند أي عميل؟ | config.php:12-13 بيوجّه الـsandbox والإنتاج لنفس العنوان — يقرا كقيمة نائبة مستنية env (والموديول ما بيقراش أي env أصلًا؛ كل الاعتماد في جدول nphies_config). وتأكّد لاحقًا (2026-08-01): ما اتصلش ولا مرة على البيئة دي — ١٢ معاملة كلهم timeout، is_active=0، والشهادات مش مرفوعة. تصحيح: nphies:poll مجدول فعلًا كل ٥ دقايق في NPHIESServiceProvider.php:55-61 — العبارة الأصلية هنا كانت غلط. |
| هل نسبة ٤١/١١٬٧٩٧ تعكس الإنتاج؟ | دي قاعدة التطوير بس. تكلفة التعبئة الرجعية (WP16) متوقّفة على الرقم الحقيقي. |
أعمدة tracking_type وvariant_type | اتحسم (2026-08-01): ليهم migrations فعلًا — في database/migrations/ الجذر مش تحت Modules/*، ومحروسين بـhasTable/hasColumn. ودي كمان النمط المعتمد لإضافة عمود على جدول تملكه وحدة تانية. |
| المتطلبات التنظيمية نفسها | قلنا إيه اللي الإيصال بيرميه مقارنة بمواصفة ZATCA وبالممارسة المعتادة — مش مقارنة بقايمة قانونية من وزارة صحة. ده مصدر تنظيمي، مش الريبو. |
الشاشة تصلح، ومش محتاجة شاشة تانية. الغالي في نقطة البيع — الحلقة — مبني وصح: باركود أولًا مع رجوع التركيز، بانر مسح غير معروف، مفاتيح F، لوحة باقي بتعيش لحد المسحة اللي بعدها، طابور أوفلاين بإعادة تشغيل آمنة، جرد أعمى عند الإقفال، وطبقة حوكمة سعر بتفرض سعرًا ثابتًا غير قابل للتعديل بأرضية — وهي بالظبط قاعدة السعر المطبوع بإسم تاني. والإضافات كلها نازلة في خانات موجودة: التشغيلة والصلاحية في سطر الميتا اللي بيعرض دلوقتي الوحدة والباركود، وتقسيم التأمين في شريط المديونية، والبديل في منتقي المتغيّرات الميت، والروشتة في الأوردر المعلَّق.
الخطر مش في الشاشة — الخطر تحتها. اختيار التشغيلة عند البيع مش موجود في Modules/POS إطلاقًا
(تعليق واحد، صفر كود، مقابل inventory_lot_balances مبني بالكامل)،
والصلاحية لا ترفض أي حاجة، والمرتجع ما بيرجّعش لوط —
و٤ من ٦ منتجات batch في بيئتك خارجة عن التطابق دلوقتي.
والصرف مش مستند، والأدوية اللي الطبيب بيكتبها غير قابلة للتخزين بالتصميم.
عشان كده الترتيب هو الخطة: صحّح الدفتر (أ) → خلّي الصلاحية ترفض (ب) → ورّي العدّاد (ج) → وحدة الصرف (د). دي ١٤ حزمة بتديك صيدلية تجزئة تشتغل صح. المريض والتأمين والروشتة والمخدِّرات بيتبنوا بعدين — على أساس صادق.
تحليل المرحلة ١ — مفيش كود ولا migration اتكتب · تلات مسارات بحث متوازية · ١ أغسطس ٢٠٢٦ · moonui2 · hazemdev2
مستنّي موافقتك على النطاق والترتيب قبل أي تنفيذ.