تقرير تشخيص فنّي — برنامج الحسابات (ERP / PHP 5.6)
المشكلة الأولى: المنتج رقم 8 «مبرد ماس 85 سم» لا يتأثر مخزونه بحركات البيع والشراء.
المشكلة الثانية: «حذف فواتير البيع لا يعمل».
قاعدة البيانات: erptest_erp — تاريخ التقرير: 2026-06-21
تحقيق للقراءة فقط — لم يتم تعديل أي بيانات أو ملفات
الخلاصة السريعة
① المنتج رقم 8 — سبب مؤكّد مشكلة حقيقية
المنتج مُعرّف في النظام على أنه «خدمة» (isService = 1) وليس صنفاً مخزنياً. تصميم البرنامج يتجاهل المخزون تماماً لأي صنف خدمة — فالفاتورة تُسجَّل لكن لا يُخصم/يُضاف أي مخزون. لذلك توقّف تأثير المنتج 8 على المخزون منذ تحويله إلى «خدمة» حوالي 23 يناير 2026، رغم أن البيع مستمرّ كل شهر.
② حذف فواتير البيع — يعمل فعلياً لا يوجد عطل في الكود
الحذف سليم على مستوى الكود وقاعدة البيانات. يوجد بالفعل 21 فاتورة محذوفة، آخرها الفاتورة رقم 338 حُذفت بنجاح اليوم (21 يونيو) من داخل البرنامج. «عدم العمل» ناتج عن حالات شرطية (زر الحذف يختفي في حالات معيّنة، أو رسالة «لا يوجد بالخزنة مبلغ كافٍ» المُضلِّلة) وليس عن خلل برمجي.
المشكلة الأولى: مخزون المنتج رقم 8 لا يتأثر حرِجة
ما هو المنتج 8؟
المعرّف
الاسم
التصنيف
isService (خدمة؟)
تاريخ الإنشاء
8
مبرد ماس 85 سم
2
1 = خدمة ✗
2025-12-20
من بين 44 منتجاً في النظام، يوجد منتجان فقط مُعرّفان كخدمة: المنتج 8 (نشِط — وهو محلّ المشكلة)، والمنتج 6 (محذوف/غير نشط).
السبب الجذري
المنتج مُعلَّم «خدمة» (isService = 1).
في كل عمليات المخزون (بيع / شراء / مرتجع بيع / مرتجع شراء) الكود يلفّ تحديث المخزون داخل شرط
if ($isService == 0). فإذا كان المنتج خدمة، يُتخطّى هذا الجزء بالكامل:
لا تحديث للرصيد في جدول storedetail، ولا تسجيل حركة في سجل المخزون storereport.
أما سطر الفاتورة نفسه (sellbilldetail) فيُسجَّل قبل وخارج هذا الشرط — لذلك الفاتورة تظهر طبيعية والمخزون لا يتحرّك.
الدليل من الكود (مواضع دقيقة)
دالة فحص الخدمة في controllers/selllfunction.php:1593:
function isService($id) {
global $myProductRecord;
$pro = $myProductRecord->load($id);
return $pro->isService;
}
مسار البيع في controllers/sellbillController.php — السطر يُسجّل الفاتورة دائماً، ثم يشترط المخزون:
// سطر 6072 — سطر الفاتورة يُسجَّل دائماً (خارج الشرط):
$detailId = $mySellbilldetailRecord->insert($mySellbilldetail);
...
// سطر 6101 — تحديث المخزون فقط لو لم يكن خدمة:
$isService = isService($productId);
if ($isService == 0) { // إذا كان صنفاً (وليس خدمة)
$productquantityAfter = decreaseProductQuantity(...); // تحديث storedetail
insertStorereportupdate($productId, $sellbillstoreId, ...,
"اضافة فاتورة مبيعات", "sellbillController.php", ...); // تسجيل storereport
}
نفس الشرط متكرّر في كل المسارات المؤثّرة على المخزون:
الشراء:buyBillController.php:2998 (وتكرارات لاحقة) وbillsfunctionsbuy.php:632 بصيغة if ($product->isService != 1).
المرتجعات: مرتجع بيع returnsellbillController.php:1455، ومرتجع شراء returnBuyBillController.php:795.
كيف صار المنتج «خدمة»؟
خانة «خدمة» تُكتب مباشرة من شاشة تعديل المنتج: productController.php:3270$isService = (int)$_POST['isService']; ثم تُحفظ في :3328.
أي أن شخصاً فتح بطاقة المنتج 8 وفعّل خانة «خدمة» وحفظ. كل حفظ لاحق للبطاقة يُعيد كتابة نفس القيمة،
لذلك تبقى الخانة مفعّلة. (حقل updatedAt يظهر اليوم لأن البطاقة حُفظت مجدداً، لكنه يعكس آخر حفظ فقط لا تاريخ التفعيل الأصلي.)
الدليل من البيانات — هنا يظهر الانقطاع بوضوح
الشهر
مبيعات فعلية على المنتج 8 (أسطر فواتير)
حركات مخزون مسجَّلة (storereport)
الحالة
ديسمبر 2025
39 سطر
42 حركة
سليم ✓
يناير 2026
5 أسطر
3 حركات (آخرها 23 يناير)
آخر يوم سليم
فبراير 2026
5 أسطر
0
توقّف ✗
مارس 2026
2 سطر
0
توقّف ✗
أبريل 2026
9 أسطر
0
توقّف ✗
مايو 2026
11 سطر
0
توقّف ✗
يونيو 2026
9 أسطر
0
توقّف ✗
بينما باقي المنتجات (9، 10، 32 …) مخزونها يتحرّك حتى اليوم 2026-06-21 — المشكلة منحصرة في المنتج 8 وحده.
الأثر المالي
234
وحدة بِيعت بعد 23 يناير ولم تُخصم من المخزون
40
سطر فاتورة مبيعات لم يؤثّر على المخزون
625
الرصيد المسجَّل (مُجمَّد) منذ 23 يناير 2026
≈391
الرصيد الحقيقي التقريبي (625 − المبيعات + المرتجعات)
تنبيه على التقارير: منذ فبراير، تقارير الأرباح وتقييم المخزون للمنتج 8 غير دقيقة — الإيراد سُجِّل بينما تكلفة البضاعة المباعة لم تُخصم. يجب تصحيح الرصيد قبل الاعتماد على أي تقرير ربحية لهذا الصنف.
الحلّ (خطوتان — كلاهما ضروري)
إلغاء صفة «خدمة»: افتح بطاقة المنتج 8 ← أزِل علامة «خدمة» ← احفظ (لتصبح isService = 0). هذا يُعيد تأثير المخزون لكل العمليات المستقبلية فوراً.
عمل تسوية مخزون (تسوية بالنقص): إلغاء الصفة لا يُعيد حركات فبراير–يونيو المفقودة بأثر رجعي. الرصيد ما زال مجمّداً عند 625. لذلك بعد إلغاء الصفة: اعمل جرد فعلي للصنف، ثم سجّل تسوية من شاشة settlementstoreController.php (نفس الأداة المستخدمة بتاريخ 18 يناير «تسوية مخزن بالنقص») لضبط الرصيد على الكمية الحقيقية. لا تُعدّل جدول storedetail يدوياً — استخدم شاشة التسوية حتى يبقى سجل المخزون متّسقاً.
ترتيب مهم: اعمل التسوية بعد إلغاء الصفة مباشرةً. لو ألغيت الصفة دون تسوية، أول عملية بيع قادمة ستخصم من الرصيد الخاطئ (625) فيستمر الخطأ.
توصية وقائية: راجع سبب تفعيل الخانة (غالباً خطأ بشري أثناء التعديل). تحقّق أيضاً إن كان المنتج 6 يجب أن يكون صنفاً حقيقياً، وافحص أي منتجات أخرى عُدِّلت في نفس جلسة 23 يناير تحسّباً لخطأ مشابه.
المشكلة الثانية: «حذف فواتير البيع لا يعمل» الحذف يعمل فعلياً
النتيجة: الحذف سليم تماماً على مستوى الكود وقاعدة البيانات. لا يوجد عطل برمجي، ولا قفل صلاحيات، ولا إعداد يمنع الحذف حالياً. الدليل القاطع: توجد 21 فاتورة محذوفة بالفعل، وآخرها الفاتورة 338 حُذفت اليوم 21 يونيو من داخل البرنامج بقيود محاسبية ومخزنية عكسية صحيحة.
إثبات أن الحذف يعمل (من قاعدة البيانات)
الحالة
عدد الفواتير
فواتير حيّة (conditions = 0)
318
فواتير محذوفة (conditions = 1)
21
أحدث عمليات الحذف الناجحة (بواسطة المستخدم رقم 1 = admin):
رقم الفاتورة
تاريخ الحذف
338
2026-06-21 00:24
327
2026-06-09 02:49
314
2026-06-05 01:42
309
2026-05-31 21:23
286
2026-05-13 19:29
الحذف هنا «حذف ناعم» (Soft delete): الفاتورة تُعلَّم conditions = 1 مع تسجيل من حذفها، وتُعكَس قيودها اليومية ومخزونها (حركة «حذف فاتورة مبيعات» في storereport). وجود هذه القيود العكسية يؤكّد أن الحذف تمّ عبر منطق البرنامج الصحيح، لا بحذف مباشر.
مسار الحذف في الكود
زر «حذف» في views/default/sellbillview/show.html:669 ← نافذة تأكيد ← «نعم»
← sellbillController.php?do=delete (سطر 3595) ← فحص الصلاحية/الدفعات (3601–3616)
← دالة delete($sellbillId) (سطر 9528) ← عكس القيود + عكس المخزون + عكس المديونية + عكس المدفوعات ← نجاح.
إعدادات التحكّم في الحذف (كلها حالياً على «السماح»)
الإعداد
القيمة الحالية
الأثر
usergroup.allowEditDelInBills
0
عمود الحذف يظهر ✓
programsettings.noBillEditTillPay
0
الزر يظهر حتى للفواتير غير المسدّدة ✓
usergroup.noBillEditIfTheirIsPayments
1
فحص منع الحذف عند وجود دفعات مُتخطّى ✓
saudielectronicinvoice / ebill
0
لا توجد خطوة فاتورة إلكترونية قد تفشل ✓
لماذا يبدو أحياناً أن «الحذف لا يعمل»؟ (الأسباب المحتملة الحقيقية)
أ) زر الحذف يختفي في حالات معيّنة: لو فُعِّل إعداد noBillEditTillPay مستقبلاً، يتحوّل الزر إلى نص «سدّد الفاتورة أولاً» لأي فاتورة غير مسدّدة بالكامل، فيظنّ المستخدم أن الحذف معطّل. (حالياً الإعداد = 0 فالزر ظاهر، لكن انتبه له.)
ب) رسالة «لا يوجد بالخزنة المبلغ الكافى لحذف الفاتورة»: في sellbillController.php:9620-9636 تظهر هذه الرسالة عند عدم كفاية رصيد الخزنة لردّ المبلغ المدفوع — لكن أمر التوقّف (return) مُعطَّل (سطر 9630)، فالحذف يكتمل فعلاً رغم الرسالة. المستخدم يقرأ الرسالة كأنها «فشل» بينما الفاتورة حُذفت بالفعل.
ج) فاتورة محذوفة مسبقاً أو حالة خاصة: محاولة حذف فاتورة سبق حذفها (لا يظهر لها زر)، أو فاتورة عليها دفعات مسدّدة مع صلاحية مانعة، أو مشكلة في مجموعة صلاحيات مستخدم مختلفة عن admin.
د) أسباب بيئية خارج الكود: خطأ JavaScript من سكربت آخر في الصفحة يُعطّل نافذة التأكيد فلا يعمل زر «نعم»، أو بروكسي يحجب التحويل، أو مشكلة صلاحية ملفات في مجلد backup/ (دالة النسخ الاحتياطي تستخدم مساراً نسبياً مع die()).
الخطوة التالية لتحديد سبب حالتك بدقّة
كرّر المشكلة بحساب المستخدم الفعلي وسجّل: رقم الفاتورة بالضبط + ماذا يظهر؟ (لا يوجد زر / «سدّد الفاتورة أولاً» / رسالة الخزنة / صفحة خطأ / لا شيء عند الضغط على «نعم»). هذه الملاحظة الواحدة تحسم السبب.
تأكّد من إعدادات مجموعة صلاحيات هذا المستخدم تحديداً: SELECT allowEditDelInBills, noBillEditIfTheirIsPayments, billDeletePassword FROM usergroup WHERE usergroupid = <مجموعته>;
لو الزر يختفي للفواتير غير المسدّدة ← السبب إعداد noBillEditTillPay أو عدم تطابق الدفع.
تحسينات مقترحة للكود (عند السماح بالتعديل لاحقاً)
في delete() (الأسطر 9620–9636): إمّا تفعيل فحص الخزنة بإيقاف حقيقي، أو إزالة الرسالة المُضلِّلة، حتى تتطابق الرسالة مع النتيجة الفعلية.
استبدال المسار النسبي fopen("backup/...") مع die() في selllfunction.php:1602 بمسار مطلق ومعالجة أخطاء سليمة.
إظهار سبب الخطأ الفعلي من الخادم (flag=2) في الواجهة بدل صفحة خطأ عامة.
خطة العمل المختصرة
#
الإجراء
الخاص بـ
الأولوية
1
افتح بطاقة المنتج 8 وألغِ علامة «خدمة» واحفظ
مشكلة المخزون
عاجل
2
اعمل جرد فعلي للمنتج 8 ثم تسوية مخزن لضبط الرصيد
مشكلة المخزون
عاجل
3
راجع المنتجات الأخرى المعدّلة حول 23 يناير
وقائي
متوسطة
4
عند ظهور «الحذف لا يعمل»: سجّل رقم الفاتورة والرسالة الظاهرة
مشكلة الحذف
عند الحدوث
5
إصلاح رسالة «الخزنة» المُضلِّلة + مسار النسخ الاحتياطي