Pharmacy POS

دراسة حالةإعادة بناء نقطة بيع ومخزونمشروع 08

مُختبَر · تكامل مستمر ناجح · غير منشور (عمل للمحفظة) · مستودع عام

لقطة شاشة · بيانات اصطناعية Pharmacy POS — لقطة شاشة لشاشة نقطة البيع، بيانات بذرة اصطناعية

الملخص

Pharmacy POS تطبيق ويب لنقطة البيع والمخزون في منضدة صيدلية: مبيعات بإيصالات قابلة للطباعة، ومخزون يُستلَم دفعاتٍ مؤرَّخة ويُصرف الأقرب انتهاءً أولًا، وموردون، وزبائن بالآجل مع تسديداتهم، وتقارير لنطاقات زمنية، ودوران للموظفين. أُعيد بناؤه انطلاقًا من نموذج المجال المرجعي لنظام صيدلية أقدم يعمل على Windows وحده ومكتوب بـ C# WinForms؛ ويصف المستودع ذلك النظام لكنه لا يربط إليه. يتبع المجال (الأدوية، والدفعات، والموردون، والمبيعات، والمدينون، والموظفون، وسجل للأحداث) النظامَ الأصلي؛ أما ما هو من عملي فإعادة البناء: المخطط المُعاد تصميمه (سنتات بأعداد صحيحة، ومخزون لكل دفعة، وأعمدة ذات أنواع محددة، ورقم إصدار للرمز) والقواعد الحاكمة للأموال والمخزون والجلسات. والجزء الهندسي الجدير بالقراءة صغير ومحدد: سنتات بأعداد صحيحة، ومخصِّص دفعات خالص داخل معاملة، وبيع لا يستطيع المتصفح تسعيره، وجلسة يستطيع الخادم إبطالها.

المشكلة والمستفيدون

تخرج منضدة الصيدلية عن المنطق المعتاد لصندوق المحل في ثلاثة مواضع: الدواء تنتهي صلاحيته فيجب تتبّع المخزون لكل دفعة وصرف الدفعة الأقرب انتهاءً أولًا؛ والمجاميع تتراكم عبر آلاف المبيعات الصغيرة فلا يجوز أن تنحرف الأموال بسبب تقريب أعداد الفاصلة العائمة؛ والزبائن يشترون بالآجل. أما التطبيق القديم لنظام ويندوز الذي أُعيد البناء منه فكان يحتفظ بإجمالي واحد للمخزون لكل دواء ويخزّن معظم القيم نصوصًا.

موظفو المنضدة (دور الصيدلي) الذين يبيعون ويسجّلون البيع بالآجل، ومدير يتحكم أيضًا في الموردين والموظفين وعمليات الحذف. هو عمل للمحفظة قابل للاستضافة الذاتية مع نسخة تجريبية مزوّدة ببذرة بيانات؛ لا نسخة مستضافة له ولا مستخدمين، ولا يتناول أي شيء في هذه الصفحة الوضع التنظيمي أو الملاءمة لصيدلية حقيقية.

دوري

عمل فردي. حدّدت النطاق ونموذج البيانات وقواعد الأموال والمخزون والجلسات، وأملك المستودع العام: 7 إيداعات بين 2026-06-14 و2026-09-06، منها حملة سبتمبر التي أصلحت ملف قفل لم يكن أي تثبيت نظيف يستطيع استخدامه، وأضافت التكامل المستمر، وعالجت التحذيرات الأمنية للاعتماديات المُبلَّغ عنها آنذاك، مع تسجيل الأعداد قبل وبعد في نصوص الإيداعات.

البنية

المخطط مرسوم من الشيفرة. المال عدد صحيح من السنتات في كل موضع من المخطط، ولا يُحوَّل إلا عند حواف النماذج والعرض. ويُحسب المخزون المتاح من الدفعات غير المنتهية الصلاحية، فتُعرض الوحدات المنتهية الصلاحية منفصلة ولا يمكن بيعها أبدًا. لا توجد طبقة وسيطة (middleware): التفويض يقيم داخل كل إجراء وكل معالج مسار.

لقطة شاشة لشاشة نقطة البيع في Pharmacy POS: أداة لاختيار الأدوية مع شارات للمخزون، وعربة فيها 12 من Paracetamol 500mg و3 من Amoxicillin 250mg، ولوحة دفع تعرض إجماليًا قدره KSh 96.00.
لقطة شاشة: شاشة نقطة البيع على بناء إنتاجي محلي، 2026-10-24. الأدوية والمورّد والأسعار من البذرة الاصطناعية الخاصة بالمستودع نفسه.
لقطة شاشة بعرض الهاتف لصفحة المخزون تسرد أربع دفعات بأرقامها والمورّد والكمية المتبقية مقابل المستلَمة، وتُظهر دفعة Paracetamol عند 488 من 500 بعد البيع.
لقطة شاشة: الدفعات بعرض الهاتف (منفذ عرض Pixel 7) بعد البيع أعلاه. يُمرَّر الجدول أفقيًا، لذا يقع عمود انتهاء الصلاحية خارج الشاشة هنا؛ وتظهر دفعة Paracetamol عند 488 / 500.

القرارات والمفاضلات

  • سنتات بأعداد صحيحة، لا تُحوَّل إلا عند الحواف. كل سعر وإجمالي ودفعة مقدّمة ورصيد عدد صحيح بالسنتات. تقرّب toCents إلى أقرب سنت وترمي استثناءً عند المدخلات السالبة أو غير المنتهية أو غير المعقولة؛ ولا ترمي formatMoney استثناءً أبدًا. والتكلفة أن كل حقل في النماذج يمرّ بتحويل، وأن سقف الـ 10 ملايين وحدة حارس فجّ لا قاعدة عمل.
  • مخزون لكل دفعة مع مخصِّص خالص بترتيب أقرب انتهاء صلاحية أولًا. كل توريد دفعة لها كمية ومتبقٍّ وتاريخ انتهاء صلاحية. ترتّب allocateFEFO بحسب تاريخ الانتهاء، وتتخطى الدفعات الفارغة، وترمي استثناءً عند عدم كفاية المخزون، ولا تعدّل مدخلاتها؛ وتستدعيها وحدة المخزون داخل معاملة المستدعي وتكتب النتيجة. والمفاضلة في تلك الكتابة: فهي تضبط قيمة متبقية مطلقة انطلاقًا من الصف الذي قرأته، لذا يستند ضمان عدم البيع بما يتجاوز المخزون إلى أن قاعدة البيانات تنفّذ المعاملات متسلسلةً. وSQLite تفعل ذلك؛ أما الانتقال إلى PostgreSQL فيحتاج أولًا إلى قفل صفوف أو تحديث مشروط.
  • المتصفح لا يسعّر البيع أبدًا. تتحقق completeSale من العربة، وتدمج البنود المكرّرة، وتعيد تحميل كل سعر من قاعدة البيانات، وتخصم المخزون، وتكتب البيع وبنوده في معاملة واحدة. ويُرفض أي خصم يتجاوز الفاتورة. والتكلفة أن المتاح في العربة قد يكون قديمًا حتى التحديث التالي، فقد يفشل بيع ثانٍ مشروع برسالة «المخزون غير كافٍ» بدل أن يُوقف في مرحلة أبكر؛ وهذا هو السلوك الذي أعادت التجربة المحلية إنتاجه.
  • جلسات يستطيع الخادم إبطالها. يحمل ملف تعريف الارتباط الموقّع لقطة من رقم إصدار الرمز للمستخدم، ويقارنها كل طلب بما في قاعدة البيانات. ويزيد الخروج وتغيير كلمة المرور وتعطيل الحساب هذا الرقم، فيتوقف أي ملف تعريف ارتباط منسوخ عن العمل. والتكلفة استعلام عن المستخدم في كل طلب، وهو ما يتجنبه رمز JWT عديم الحالة المجرّد.

نتائج مُتحقَّق منها

المقياسالنتيجةالدليل
اختبارات الوحدة في التكامل المستمر24 ناجحًا في 2 من الملفات (الأموال 16، وتخصيص الدفعات 8)تشغيل التكامل المستمر 34045206132، main 6f04341، 2026-09-06؛ وأُعيد التشغيل على نسخة جديدة في 2026-10-24. دوال خالصة، دون قاعدة بيانات
التدقيق اللغوي للشيفرة، وفحص الأنواع، والبناء الإنتاجيكلها نظيفةبوابات في سير العمل نفسه؛ وأُعيدت محليًا بتاريخ 2026-10-24
سكربت فحص قاعدة البيانات3 / 3 فحوص (خُصم المخزون بمقدار 7 بالضبط، وحُفظ البيع، والإجمالي = السعر × الكمية)، ثم تراجع عن عمليات الكتابة التي أجراهاscripts/test-sale.mjs على قاعدة SQLite المزوّدة بالبذرة، 2026-10-24. يكرّر عمليات البيع على قاعدة البيانات بدل استدعاء الدالة المستخدمة في الإنتاج
سجل التكامل المستمرفشل التشغيل الأول ← 3 تشغيلات ناجحةفشل التشغيل 33656928745 (2026-09-02) عند npm ci بسبب مدخل في ملف القفل خاص بمنصة بعينها؛ وأُصلح بإعادة توليد ملف القفل دون وجود node_modules (1f76113)
التحذيرات الأمنية للاعتماديات1 متوسط الخطورة + 10 عالية الخطورة ← 0 بحلول 2026-09-06؛ و8 مفتوحة في 2026-10-24أعداد سبتمبر من رسائل الإيداعات 1f76113 وaa59f16 و6f04341. وأظهر npm audit على ملف القفل نفسه دون تغيير بتاريخ 2026-10-24: 2 متوسطة، و5 عالية، و1 حرج (الحرج في next)، فهذه تحذيرات أُضيفت منذ ذلك الحين
التجربة المحليةبيع ← تأكيد خصم المخزون في قاعدة البيانات ← لوحة المعلومات ← إيصال PDF (200، application/pdf) ← تصدير CSV ← رفض الصيدلي عند /staff ← المسارات غير المصادَق عليها تعيد 401بناء إنتاجي على localhost بالبذرة الاصطناعية، 2026-10-24؛ ولقطات الشاشة في هذه الصفحة
فحص المخزون من جهة الخادمعربتان حملت كلٌّ منهما آخر 5 وحدات؛ نجح البيع الأول، ورُفض الثاني، وانتهى المخزون عند 0الجلسة نفسها. كانت الطلبات متتابعة، لذا يثبت هذا إعادة الفحص، لا السلوك تحت تزامن فعلي
فجوة مكتشفة في دليل البدء السريعnpm run seed يفشل دون تصدير متغير قاعدة البيانات؛ وnpx prisma db seed يعملنسخة جديدة، 2026-10-24: تحمّل واجهة Prisma السطرية ملف .env ولا يحمّله عميل Prisma 6. ولا يتأثر التطبيق نفسه

القيود والعمل المتبقي

  • غير منشور. لا يحتوي المستودع على إعدادات استضافة ولا ملف حاوية ولا سجل نشر، ولا توجد نسخة مستضافة ولا مستخدمون، ولا يتناول أي شيء هنا الوضع التنظيمي أو الملاءمة لصيدلية حقيقية.
  • تتوقف تغطية الاختبارات عند الوحدتين الخالصتين. ليس لإجراءات الخادم الـ 16 ولا للصفحات ولا لمسارَي PDF وCSV ولا لتدفق تسجيل الدخول أي اختبارات آلية؛ والتجربة أعلاه فحص يدوي، لا حزمة اختبارات.
  • ضمان عدم البيع بما يتجاوز المخزون غير مثبت تحت حمل متزامن، ويحتاج تحديث الدفعات إلى قفل صفوف أو تحديث مشروط قبل أن يمكن نقل قاعدة البيانات إلى PostgreSQL، وهو ما يصفه README بأنه ممكن لكنه لم يُجرَّب.
  • التحكم بالأدوار في الشيفرة أقل صرامة مما يذكره README: يستطيع الصيدلي إنشاء الأدوية وتعديلها واستلام المخزون، لا عرضها فقط. ولا يقتصر على المدير إلا الحذف والموردون والموظفون.
  • الاعتماديات متأخرة من جديد: يبلّغ npm audit بتاريخ 2026-10-24 عن 8 تحذيرات مفتوحة، 1 منها حرج في next، وتحتاج إلى ترقية قبل أي استخدام فعلي.
  • يمكن إتمام بيع بمبلغ مدفوع أقل من الإجمالي: يُسجَّل برصيد مستحق ودون سجل مدين، فلا تُتتبَّع المبالغ غير المدفوعة خارج مسار البيع بالآجل.
  • لا إرجاع ولا استرداد ولا إلغاء للبيع ولا تعديل للمخزون سوى حذف دفعة كاملة؛ والزبون بالآجل هو دواء واحد بالآجل لا حساب زبون؛ وتستخدم المبيعات سعر الدواء في الكتالوج، الذي يتبع أحدث دفعة، لا سعر البيع الخاص بالدفعة نفسها.
  • سجل التدقيق يُكتب لكنه لا يُعرض أبدًا؛ ولا تسجّل إجراءات إنشاء الأدوية والموردين وتعديلها أي شيء على الإطلاق، ويُكتب صف التدقيق الخاص بالبيع بعد اعتماد معاملته، فإن فشلت كتابته بقي بيع بلا مدخل في السجل. ويأتي دليل البدء السريع الموثّق بحسابَي دخول افتراضيين يطلب README من المشغّل تغييرهما.