أربع مسارات تتبّع متوازية (الباك-إند · الواجهة وظيفيًا · الاختبارات والانحدارات · التصميم والتجربة). ٣٤ نتيجة، أهم أربعتهم مؤكَّدين من مصدرين مستقلّين، واتنين اتحسموا بقراءة قاعدة بيانات بيئتك. الخلاصة: العيوب مش ٣٤ مشكلة منفصلة — هي ست عائلات لكل واحدة سبب واحد.
بلاغك كان: «حصلت تعديلات كتيرة وبقى في مشاكل في تقريبًا أغلب النقاط اللي فيها، زي الدفع». التشخيص حوّل ده لأعراض محدّدة وقابلة للاختبار:
| المكان | المتوقَّع | الواقع |
|---|---|---|
| نافذة الدفع الكاملة | تدفع كاش + شبكة بمبلغ ومرجع | بترجع خطأ ٤٢٢ دايمًا — ما تقدرش تكمّل بيعة منها إطلاقًا |
| مفاتيح F | F10 كاش · F11 شبكة · F1 دفع | ميتة طول ما التركيز على خانة الباركود — وده وضع الراحة المصمَّم |
| خصم / كوبون | الفاتورة تطلع بالمخصوم | الشاشة تاخد ٩٠ والفاتورة تتعمل ١٠٠ ⇒ مديونية وهمية دائمة |
| الضريبة | تتحصّل وتتسجّل | تتحصّل من الزبون وما تتسجّلش على الفاتورة ولا في حساب الضريبة |
| الباقي | يتسجّل باقي ويتعرض | المدفوع كله بيتسجّل نقدية ⇒ الخزينة أكبر من الدرج، والباقي يختفي من الشاشة بعد التأكيد |
| إقفال الوردية | متوقّع مقابل فعلي | وردية فيها شبكة مستحيل تتقفل · والمتوقّع بيحسب مدفوعات كاشير تاني · والفرق دايمًا صفر |
| التعليق (F4) والمرتجع | يشتغلوا | بيفشلوا في صمت تام — لا رسالة ولا تغيير |
| الأوفلاين | يزامن بأمان | ممكن يكرّر البيعة، وممكن يحجز الكاش للأبد لو الوردية اتقفلت |
| المخزون (على بيئتك) | يتخصم عند البيع | البضاعة تخرج والمخزون ما يتخصمش والتكلفة ما تتسجّلش لحد ما أمين مخزن يعتمد |
الـPOS مضبوط على شركة «بلاك سيركل» (حسابات الاستلام والعميل والمخزن كلها متظبّطة) لكن فيه ورديتين وصفر عملية بيع فعلية — فمافيش صف بيانات غلط أقدر أوريهولك. التشخيص كله تتبّع كود بأدلة (مسار + سطر)، وأربع نتائج منه اتأكّدت من مسارين مستقلّين (السيرفر والواجهة وصلوا لنفس النتيجة كل واحد لوحده)، واتنين اتحسموا بقراءة إعدادات قاعدة بياناتك فعلًا. اللي محتاج إعادة حيّة معلَّم صراحةً في الجدول.
الشاشة (Angular · signals، مفيش NgRx)
features/pos/pos-layout.component.ts ← الحلقة والاختصارات والأوفلاين
├─ pos-header (باركود) ├─ invoice-table (السلة) ├─ numpad
└─ payment-dialog ├─ session-close-dialog ├─ return-dialog ├─ coupon-dialog
│
├─ pos-cart.service.ts → toInvoicePayload() ← هنا بتتبني الحمولة
└─ pos.service.ts → POST /api/pos/sales
│
Modules/POS/app/Http/Requests/StorePOSSaleRequest.php ← ⚠️ البوابة الصامتة
validated() بيرمي أي مفتاح مش مذكور في القواعد — من غير أي خطأ
│
Modules/POS/app/Http/Controllers/POSSaleController.php::store() [DB::transaction]
├─ يبني فاتورة مبيعات بحالة Approved (بيختم الاعتماد بنفسه)
├─ Modules\Sales\Actions\PostSalesInvoice ← الإيراد/الضريبة/التكلفة/المخزون
└─ لكل دفعة: resolveReceivingAccount() ثم SalesPayment::create() + PostSalesPayment
│
قيود اليومية
validated() بوابة صامتة. أي حقل الواجهة بتبعته ومش مذكور في قواعد الطلب
بيتشال بهدوء — مفيش خطأ، مفيش لوج. الفاتورة بتتعمل بدونه وكأن الكاشير ما دخلوش.StoreSalesInvoiceRequest وSalesPaymentController — وفيهم حراس موجودين وشغّالين.
الـPOS بينادي الـActions والموديل مباشرة ⇒ بيورث المحاسبة، وما بيورثش الحراسة.ثمن حالات، نفس الآلية: الواجهة بتبعت مفتاح، السيرفر مش عارفه، validated() بيرميه في صمت.
| الواجهة بتبعت | السيرفر بيقبل | النتيجة | التأكيد |
|---|---|---|---|
| session_id | pos_session_id (required) | نافذة الدفع الكاملة ميتة — ٤٢٢ دايمًا | مسارين ✔✔ |
| tax_rate + tax_amount | tax_rate_id فقط | الضريبة تتحصّل ولا تتسجّل [FIN] | مسارين ✔✔ |
| discount_amount (رأس) | — مفيش — | الخصم يتشال ⇒ مديونية وهمية [FIN] | مسارين ✔✔ |
| variant_id | product_variant_id | اللون/المقاس يتشال من كل بيعة والمخزون يتخصم من الأب | مسارين ✔✔ |
| bank_transfer | in:cash,card,credit,check | ٤٢٢ والبيعة كلها تفشل | مسارين ✔✔ |
| credit_card (إقفال الوردية) | card | وردية فيها شبكة مستحيل تتقفل، ومتوقّع الشبكة = صفر | مسارين ✔✔ |
| name · qty (تعليق) | product_name · quantity · line_total | التعليق بيفشل — وبلا معالج خطأ فبيفشل في صمت | الواجهة ✔ |
| return_qty (مرتجع) | quantity + date مطلوب | المرتجع بيفشل في صمت برضه | الواجهة ✔ |
| الحارس | موجود فين وشغّال | الـPOS |
|---|---|---|
| منع الدفع الزائد | SalesPaymentController.php:116-120 · :184-189 (٤٢٢ لو الدفع > المستحق) | بينادي SalesPayment::create() مباشرة ⇒ الزيادة بتتسجّل نقدية |
| أرضية أقل سعر بيع | StoreSalesInvoiceRequest.php:102-125 ( allow_below_min_price = مقفول عندك) | قاعدة عارية numeric,min:0 ⇒ بيع بأي سعر |
| دورة الاعتماد | sales.approval_workflow | بيختم Approved على نفسه |
| منع السالب في المخزون | ApproveIssue.php:66-131 | decreaseStock() مباشرة — بلا أي فحص توفّر |
والخبر الحلو: الحراس دول مكتوبة ومختبَرة ومطبَّقة في المبيعات — فالإصلاح نسخ شكل موجود، مش تصميم من الصفر.
pos_session_id — اتأكّد على قاعدة بياناتك ⇒ الوردية لسه متوقّعة الكاش الأصلي كامل، وتقارير الـPOS ما بتخصمش المرتجعات إطلاقًا.النتيجة المركّبة: أي يوم فيه مرتجع واحد أو زيادة واحدة = فرق خزينة مضمون، وما فيش مكان في النظام بيشرح الفرق.
ده أهم اكتشاف في المراجعة كلها، لأنه بيفسّر كل اللي فوق:
track_inventory = false) ⇒ جسم المخزون والتكلفة في PostSalesInvoice مش بيتنفّذ أصلًا.stock_deduction_point بيرجع null ⇒ نفس الجسم مش بيتنفّذ — للمرة التانية، بآلية مستقلة.وطرفة مقلقة: ملف اختبار الـPOS هو المكان الوحيد في المشروع كله اللي بينفّذ PostSalesPayment بنجاح — يعني الأكشن اللي بيحرّك النقدية للأستاذ العام له ملف اختبار واحد، وهو الملف اللي مش بيغيّر المبلغ أبدًا.
| العيب | الأثر | الخطورة |
|---|---|---|
| الأوردرات المعلّقة بلا فحص شركة — العرض والاسترجاع والحذف | أي مستخدم يقرا سلة شركة تانية بالكامل (العميل والأصناف والأسعار). كل شاشات الـPOS التانية بتفحص — دي الوحيدة اللي فاتت | P0 أمان |
| الأوفلاين بلا مفتاح تفرّد — ومهلة بعد نجاح التسجيل بتتعامل كأنها فشل | بيعة مكرّرة وخصم مخزون مرتين · وبيعة آخر الوردية مستحيل ترحّل بعد الإقفال ⇒ كاش محبوس · والعنصر الفاشل بيفضل للأبد بلا شاشة | P0 |
| الكوبون باطل في يوم بدايته — مقارنة تاريخ بنص | الكوبون يشتغل بكرة (مؤكَّد بالتشغيل الفعلي) | P2 |
receipt_number بلا تفرّد — والبحث بياخد الأول | إيصالان بنفس الرقم ⇒ الاستعلام يرجّع الغلط | P2 |
| ورديتان على جهاز واحد — فحص-ثم-كتابة بلا قفل ولا مفتاح فريد | سباق نادر بيفتح ورديتين | P3 |
ورث الـActions (الترحيل والمحاسبة) وما ورثش الـFormRequest (الحراسة والتحقّق). كل عيوب عائلة ب سببها القرار المعماري ده. مش انحدار — فجوة أصلية من يوم ما الموديول اتكتب.
validated() بيفشل في صمت — والصمت هو المشكلةلو Laravel كان بيرمي خطأ على أي مفتاح غير معروف، الثمن حالات في عائلة أ كانت هتظهر في أول يوم. بدل كده، الفاتورة بتتعمل «بنجاح» وناقصة ضريبة أو خصم. ده مش عيب في Laravel — ده غياب اختبار عقد بين الطرفين.
منتج خدمة + إعدادات غير مزروعة = المسار الحقيقي مش بيتنفّذ. السويت خضرا، والكود اللي بتحميه ما اتنفّذش ولا مرة. وده اللي خلّى «التعديلات الكتيرة» تعدّي: كل تعديل كان بيعدّي على سويت بتقول «تمام» وهي عمياء.
شركتك مضبوطة على cogs_recognition_point = delivery — ميزة اتعملت لمسار المبيعات (تسليم جزئي).
في الوضع ده، ترحيل الفاتورة بيأجّل خصم المخزون والتكلفة لحد اعتماد إذن الصرف — وده صحيح تمامًا لمندوب توصيل
وغلط تمامًا لكاونتر الزبون بياخد البضاعة منه فورًا. الـPOS ما اتظبطش أبدًا على الوضع ده.
ده انحدار بالإعداد لا بالكود: ما اتغيّرش سطر في الـPOS، اتغيّر العالم حواليه.
| البُعد | النتيجة |
|---|---|
| البيانات | مفيش صفوف فاسدة تتصلّح — صفر بيعة POS على بيئتك. لو النظام اشتغل على نسخة عميل، الفواتير المتأثرة هتبان بـis_pos = 1 مع tax_amount = 0 ودفعات أكبر من الإجمالي. ما اتنفّذش أي استعلام على نسخة عميل. |
| مسار الكود | كل نداءات PostSalesInvoice وPostSalesPayment اتفحصت: شاشة المبيعات بتعدّي على الحراس، الـPOS لأ. الـPOS هو المتخطّي الوحيد. |
| الإعدادات | مؤكَّد من قاعدة بياناتك: cogs_recognition_point=delivery · auto_create_stock_issue=1 · auto_approve=0 · allow_below_min_price=0 · sales.cash_account_id غير موجود أصلًا (فالاحتياطي الموثَّق كود ميت). حسابات الـPOS الثلاثة مضبوطة، فمفيش انهيار ٥٠٠ عندك. |
| الصلاحيات | كل شاشات الـPOS متبوّبة صح؛ الاستثناء الوحيد: الأوردرات المعلّقة بلا فحص شركة. وبيع الـPOS متبوّب على sales.invoices.create مش صلاحية POS مستقلة. ومفاتيح F بتتخطّى إخفاء الأزرار ⇒ ٤٠٣ بدل زرار مخفي. |
| الفلوس [FIN] | الضريبة · الخصم · الزيادة · المرتجع · التكلفة · حد أقل سعر · حد الائتمان (غير مطبَّق في المبيعات ولا الـPOS). كل واحد فيهم يقدر يغيّر رقم في الأستاذ العام. |
| الترجمة والاتجاه | نظيفة — ١٩٦ مفتاح في اللغتين، صفر ناقص. الاستثناءات: «عميل نقدي» متحطّة بالإيد · الإيصال وتقرير الإقفال عربي إجباري حتى في الإنجليزي · محاذاة الجدول ثابتة يمين · نسخة وهمية v2.4.0. |
| الحالات الحدّية | دفع صفر يعمل قيد صفر/صفر · طريقة credit ما بتتطابقش مع أي حاجة عند الإقفال · الكمية العشرية مستحيلة في الـPOS (بيقطع الكسر) رغم إن السيرفر بيقبلها · إدخال ٠ في الكمية بيحذف السطر بلا تأكيد. |
| الأشقّاء الكامنون | الأهم: فحص ملكية المتغيّر (ValidatesLineVariantOwnership) متطبَّق على ~٣٠ FormRequest عبر المبيعات والمشتريات والمخزون — وStorePOSSaleRequest هو المستند الوحيد المتبقّي بالفحص العاري. المسح اللي قفل ده في كل مكان (تذكرتَي المتغيّرات) فات الـPOS. وكذلك: قبول معرّفات من شركة تانية بقواعد exists عارية موجود في المبيعات كمان (فجوة على مستوى الموديولات). |
اللحظات التلاتة اللي بتعرّف أي POS مكسورة: تدفع من الكيبورد · تاخد كاش وترجّع باقي · تمسح للزبون اللي بعده.
أفضل حالة لبيعة كاش بالظبط النهارده: امسح ← دوس في مكان فاضي ← F10 ← Enter ← اقفل نافذة الطباعة ← دوس على خانة الباركود. تلاتة من الست خطوات دي عيوب صافية.
| العيب | التكلفة على الكاشير |
|---|---|
| مفاتيح F ميتة في وضع الراحة — الحارس بيخرج على أي حقل إدخال، والتصميم مثبّت التركيز على الباركود | ضغطة زيادة كل بيعة + وعد مكتوب على الزرار «(F10)» مش بيتنفّذ |
| الباقي بيختفي وقت ما محتاجه — بيتحسب، وبعد التأكيد الرسالة بتقول رقم الفاتورة بس | الكاشير بيعدّ الفكّة والشاشة فاضية — أرخص إصلاح وأعلى عائد |
| مفيش فئات نقدية، والخانة متعبّية بالمبلغ بالظبط | الزبون يدّي ٢٠٠ على ١٤٥ ⇒ امسح واكتب |
| التركيز ما بيرجعش للباركود + الطباعة بتفتح نافذة بتخطف التركيز | أول مسحة للزبون الجاي بتضيع |
| باركود مش معروف = صمت | لا صوت ولا رسالة ولا وميض |
| «إقفال الوردية» جنب «ادفع» بخط ٩ بكسل | إجراء مرة في اليوم ملزوق في منطقة الفلوس، وكلهم تحت حد اللمس |
| الإجمالي في القطر المعاكس لزرار الدفع (٢٨ بكسل أسفل لوحة الفاتورة) | نقلة عين طويلة كل بيعة |
| الشاشة برّه نظام الثيم — ألوان وخط بالإيد، والوضع الليلي كود ميت (كلاس غلط) | إعدادات الخط والكثافة والوضع الليلي مالهاش أي أثر في الـPOS |
| أدوات تطوير ظاهرة للكاشير — زرار «محاكاة عدم الاتصال» ورقم فاتورة عشوائي ونسخة وهمية | الرقم المعروض مش رقم الفاتورة الحقيقي وثابت طول الوردية |
┌──────────────────────────────────────────────────────────────────────┐
│ 🛒 كاشير ١ │▮ امسح الباركود… 🔍│ ●متصل حازم ⏻ │
└──────────────────────────────────────────────────────────────────────┘
↑ الباركود ياخد أعرض مساحة، وحلقة تركيز دايمة ↑ إقفال الوردية اتنقل هنا
┌──────────────────────────────────┬───────────────────────────────────┐
│ الفاتورة [عميل: نقدي ▾] [تعليق]│ ┌─────────────────────────────┐ │
│ ─────────────────────────────────│ │ الإجمالي │ │
│ ١ حليب المراعي [−] ٢ [+] ٢٤ │ │ ١٤٥.٥٠ ر.س │ │ ٤٨-٥٦px
│ ٢ خبز بلدي [−] ١ [+] ٥ │ └─────────────────────────────┘ │
│ ▒٣▒ شاي ليبتون ✱ [−] ٣ [+] ٥٤ │ ┌─────────────────────────────┐ │
│ ✱ آخر سطر بيومض أخضر │ │ 💵 دفع نقدي (F10) │ │ ٧٢px
│ ─────────────────────────────────│ └─────────────────────────────┘ │
│ فرعي ١٤٥.٥٠ · خصم ٠ · ضريبة ٠ │ [💳 شبكة F12] [مختلط F1] │
└──────────────────────────────────┴───────────────────────────────────┘
أهم قرارين: الإجمالي انتقل جنب زرار الدفع — تقرا الرقم وتضغط تحته، منطقة نظر واحدة تقفل البيعة. و«دفع نقدي» بقى أكبر عنصر في الشاشة لأنه ~٨٠٪ من العمليات — الحجم هو التراتب.
┌───────────── دفع نقدي ──────────── ✕ ┐
│ الإجمالي ١٤٥.٥٠ ر.س │
│ المدفوع من العميل │
│ ┌─────────────────────────────────┐ │ ٥٦px · بيتحدّد كله عند التركيز
│ │ ٢٠٠.٠٠ │ │ فالكتابة بتستبدل
│ └─────────────────────────────────┘ │
│ [المبلغ بالضبط][٥٠][١٠٠][٢٠٠][٥٠٠] │ ٦٤px فئات
│ ▓▓▓ الباقي ٥٤.٥٠ ر.س ▓▓▓ │ ظاهر دايمًا — أحمر لو ناقص
│ ┌─────────────────────────────────┐ │
│ │ ✓ تأكيد وطباعة (Enter) │ │ ٦٤px
│ └─────────────────────────────────┘ │
└───────────────────────────────────────┘
↓ بعد التأكيد تتحوّل لـ ↓
┌───────────────────────────────────────┐
│ ✓ تم البيع #INV-000123 │
│ الباقي للعميل │
│ ٥٤.٥٠ ر.س │ ٧٢px — يتقري من برّه الكاونتر
│ (يقفل تلقائيًا عند أول مسحة جديدة) │
└───────────────────────────────────────┘
نصلّح الثمن أسماء حقول والحراس، ونسيب المعمار زي ما هو.
+ أسرع حاجة توقف النزيف.
− ما بتمنعش الحالة التاسعة — أول حقل جديد يتضاف هيتشال في صمت زي إخوانه.
نفس إصلاحات (أ)، وزيادة عليها: اختبار واحد بيقارن الحمولة اللي الواجهة بتبنيها بقواعد السيرفر ويفشل على أي مفتاح مرفوض، والطلب يرفض المفاتيح المجهولة صراحةً بدل ما يرميها.
+ بيقفل العائلة مش الحالات. تكلفته الزيادة يوم واحد.
− محتاج قرار: نرفض المفتاح المجهول ولا نسجّله ونكمّل.
الـPOS ينادي نفس الـFormRequest بتاع المبيعات ⇒ يورث كل الحراس تلقائيًا وللأبد. + الأنضف معماريًا. − أسابيع، وبيمسّ مسار مالي شغّال — مش دلوقتي؛ بس (ب) بيخلّيه ممكن بعدين.
| WP | النطاق | الطبقة | يثبته اختبار | وسم |
|---|---|---|---|---|
| WP0 | اختبار تثبيت أولًا: منتج متتبَّع للمخزون + زرع الإعدادات في اختبارات الـPOS ⇒ يفضح إن مسار المخزون/التكلفة ما كانش بيتنفّذ، ويمنع الرجوع | BE tests | فشل-ثم-نجاح | P0 |
| WP1 | الثمن أسماء حقول: رقم الوردية · الضريبة (معرّف شريحة) · خصم الرأس · المتغيّر · التحويل البنكي · تسمية الشبكة عند الإقفال · التعليق · المرتجع | BE + FE | اختبار عقد لكل حمولة | P0 [FIN] |
| WP2 | الحراس المتخطَّاة: منع الدفع الزائد (نسخ شكل المبيعات) · أرضية أقل سعر · فحص ملكية المتغيّر · فحص الشركة على المعرّفات | BE | لكل حارس | P0 [FIN] |
| WP3 | ثغرة الأوردرات المعلّقة: فحص شركة على العرض/الاسترجاع/الحذف | BE | عزل شركات | P0 أمان |
| WP4 | الـPOS في وضع التسليم: بيعة الكاونتر تخصم المخزون وتسجّل التكلفة فورًا بغضّ النظر عن cogs_recognition_point (بإعداد صريح للـPOS) | BE | الوضعين | P0 [FIN] |
| WP5 | حلقة الكاشير (اليوم الواحد): مفاتيح F تعدّي الحارس · التركيز يرجع للباركود + طباعة بإطار داخلي · لوحة الباقي بعد البيع + صوت ونتيجة المسح · فئات نقدية + عرض الباقي/النقص دايمًا | FE | بناء + تجربة | P1 |
| WP6 | مسار مرتجع POS حقيقي: نقطة نهاية + ربط بالوردية (عمود جديد) + خصم من متوقّع الكاش + ظهور في التقارير | BE + FE · مايجريشن | الوردية والتقارير | P1 [FIN] |
| WP7 | إقفال الوردية: يستخدم ملخّص السيرفر الجاهز · إدخال الفعلي فاضي (جرد أعمى) · يسمح بإقفال بلا سطر كاش | FE + BE | إقفال بشبكة | P1 [FIN] |
| WP8 | أمان الأوفلاين: مفتاح تفرّد يوصل السيرفر · المهلة تتعامل كـ«غير مؤكَّد» لا «فشل» · شاشة لإدارة الطابور الفاشل | BE + FE | إعادة إرسال مكرّرة | P1 [FIN] |
| WP9 | حوكمة السعر في الواجهة: قراءة allow_price_override وallow_discount وrequire_customer وأرضية أقل سعر | FE | — | P1 |
| WP10 | التصميم الهيكلي: الإجمالي جنب الدفع · إعادة تصميم نافذة الدفع · دمج الثيم والوضع الليلي · إصلاح لوحة الأرقام · حالات مصمَّمة (أوفلاين/صلاحية/تحميل) | FE | — | P2 |
| WP11 | النظافة: شيل أدوات التطوير ورقم الفاتورة الوهمي · الإيصال (تاريخ، طريقة دفع مترجمة، ثنائي اللغة، رقم ضريبي و QR) · تاريخ الكوبون · تفرّد رقم الإيصال · قفل الورديتين | BE + FE | الكوبون والتفرّد | P3 |
WP0 قبل أي حاجة — من غيره كل إصلاح بعده بيتراجع على سويت عمياء. بعدها WP1→WP4 (يوقفوا تسريب الفلوس والثغرة الأمنية)، وبعدها WP5 لوحده لأنه اليوم الواحد اللي هتحسّ بيه أكتر حاجة على الكاونتر، وبعدين الباقي بالترتيب.
الـPOS مش محتاج إعادة بناء. محاسبته ذرّية، وحساب سطوره مطابق للسيرفر، وترجمته كاملة، وفكرته سليمة. اللي مكسور الوصلات: ثمن حقل بتتشال في صمت، وأربع حراس بيتخطّاهم، ودرج مش مربوط بدفتر، وحلقة استخدام فيها تلات ضغطات ضايعة. وأخطر من ده كله إن الاختبارات كانت بتحرس ممر فاضي — وعشان كده WP0 قبل أي إصلاح: لازم الاختبار يشوف المسار الحقيقي الأول، وبعدين نصلّح.