تقرير تشخيص فنّي — برنامج الحسابات (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 سم21 = خدمة ✗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
}

نفس الشرط متكرّر في كل المسارات المؤثّرة على المخزون:

كيف صار المنتج «خدمة»؟ خانة «خدمة» تُكتب مباشرة من شاشة تعديل المنتج: productController.php:3270 $isService = (int)$_POST['isService']; ثم تُحفظ في :3328. أي أن شخصاً فتح بطاقة المنتج 8 وفعّل خانة «خدمة» وحفظ. كل حفظ لاحق للبطاقة يُعيد كتابة نفس القيمة، لذلك تبقى الخانة مفعّلة. (حقل updatedAt يظهر اليوم لأن البطاقة حُفظت مجدداً، لكنه يعكس آخر حفظ فقط لا تاريخ التفعيل الأصلي.)

الدليل من البيانات — هنا يظهر الانقطاع بوضوح

الشهرمبيعات فعلية على المنتج 8
(أسطر فواتير)
حركات مخزون مسجَّلة
(storereport)
الحالة
ديسمبر 202539 سطر42 حركةسليم ✓
يناير 20265 أسطر3 حركات (آخرها 23 يناير)آخر يوم سليم
فبراير 20265 أسطر0توقّف ✗
مارس 20262 سطر0توقّف ✗
أبريل 20269 أسطر0توقّف ✗
مايو 202611 سطر0توقّف ✗
يونيو 20269 أسطر0توقّف ✗

بينما باقي المنتجات (9، 10، 32 …) مخزونها يتحرّك حتى اليوم 2026-06-21 — المشكلة منحصرة في المنتج 8 وحده.

الأثر المالي

234
وحدة بِيعت بعد 23 يناير
ولم تُخصم من المخزون
40
سطر فاتورة مبيعات
لم يؤثّر على المخزون
625
الرصيد المسجَّل (مُجمَّد)
منذ 23 يناير 2026
≈391
الرصيد الحقيقي التقريبي
(625 − المبيعات + المرتجعات)
تنبيه على التقارير: منذ فبراير، تقارير الأرباح وتقييم المخزون للمنتج 8 غير دقيقة — الإيراد سُجِّل بينما تكلفة البضاعة المباعة لم تُخصم. يجب تصحيح الرصيد قبل الاعتماد على أي تقرير ربحية لهذا الصنف.

الحلّ (خطوتان — كلاهما ضروري)

  1. إلغاء صفة «خدمة»: افتح بطاقة المنتج 8 ← أزِل علامة «خدمة» ← احفظ (لتصبح isService = 0). هذا يُعيد تأثير المخزون لكل العمليات المستقبلية فوراً.
  2. عمل تسوية مخزون (تسوية بالنقص): إلغاء الصفة لا يُعيد حركات فبراير–يونيو المفقودة بأثر رجعي. الرصيد ما زال مجمّداً عند 625. لذلك بعد إلغاء الصفة: اعمل جرد فعلي للصنف، ثم سجّل تسوية من شاشة settlementstoreController.php (نفس الأداة المستخدمة بتاريخ 18 يناير «تسوية مخزن بالنقص») لضبط الرصيد على الكمية الحقيقية. لا تُعدّل جدول storedetail يدوياً — استخدم شاشة التسوية حتى يبقى سجل المخزون متّسقاً.
ترتيب مهم: اعمل التسوية بعد إلغاء الصفة مباشرةً. لو ألغيت الصفة دون تسوية، أول عملية بيع قادمة ستخصم من الرصيد الخاطئ (625) فيستمر الخطأ.
توصية وقائية: راجع سبب تفعيل الخانة (غالباً خطأ بشري أثناء التعديل). تحقّق أيضاً إن كان المنتج 6 يجب أن يكون صنفاً حقيقياً، وافحص أي منتجات أخرى عُدِّلت في نفس جلسة 23 يناير تحسّباً لخطأ مشابه.

المشكلة الثانية: «حذف فواتير البيع لا يعمل» الحذف يعمل فعلياً

النتيجة: الحذف سليم تماماً على مستوى الكود وقاعدة البيانات. لا يوجد عطل برمجي، ولا قفل صلاحيات، ولا إعداد يمنع الحذف حالياً. الدليل القاطع: توجد 21 فاتورة محذوفة بالفعل، وآخرها الفاتورة 338 حُذفت اليوم 21 يونيو من داخل البرنامج بقيود محاسبية ومخزنية عكسية صحيحة.

إثبات أن الحذف يعمل (من قاعدة البيانات)

الحالةعدد الفواتير
فواتير حيّة (conditions = 0)318
فواتير محذوفة (conditions = 1)21

أحدث عمليات الحذف الناجحة (بواسطة المستخدم رقم 1 = admin):

رقم الفاتورةتاريخ الحذف
3382026-06-21 00:24
3272026-06-09 02:49
3142026-06-05 01:42
3092026-05-31 21:23
2862026-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.allowEditDelInBills0عمود الحذف يظهر ✓
programsettings.noBillEditTillPay0الزر يظهر حتى للفواتير غير المسدّدة ✓
usergroup.noBillEditIfTheirIsPayments1فحص منع الحذف عند وجود دفعات مُتخطّى ✓
saudielectronicinvoice / ebill0لا توجد خطوة فاتورة إلكترونية قد تفشل ✓

لماذا يبدو أحياناً أن «الحذف لا يعمل»؟ (الأسباب المحتملة الحقيقية)

أ) زر الحذف يختفي في حالات معيّنة: لو فُعِّل إعداد noBillEditTillPay مستقبلاً، يتحوّل الزر إلى نص «سدّد الفاتورة أولاً» لأي فاتورة غير مسدّدة بالكامل، فيظنّ المستخدم أن الحذف معطّل. (حالياً الإعداد = 0 فالزر ظاهر، لكن انتبه له.)
ب) رسالة «لا يوجد بالخزنة المبلغ الكافى لحذف الفاتورة»: في sellbillController.php:9620-9636 تظهر هذه الرسالة عند عدم كفاية رصيد الخزنة لردّ المبلغ المدفوع — لكن أمر التوقّف (return) مُعطَّل (سطر 9630)، فالحذف يكتمل فعلاً رغم الرسالة. المستخدم يقرأ الرسالة كأنها «فشل» بينما الفاتورة حُذفت بالفعل.
ج) فاتورة محذوفة مسبقاً أو حالة خاصة: محاولة حذف فاتورة سبق حذفها (لا يظهر لها زر)، أو فاتورة عليها دفعات مسدّدة مع صلاحية مانعة، أو مشكلة في مجموعة صلاحيات مستخدم مختلفة عن admin.
د) أسباب بيئية خارج الكود: خطأ JavaScript من سكربت آخر في الصفحة يُعطّل نافذة التأكيد فلا يعمل زر «نعم»، أو بروكسي يحجب التحويل، أو مشكلة صلاحية ملفات في مجلد backup/ (دالة النسخ الاحتياطي تستخدم مساراً نسبياً مع die()).

الخطوة التالية لتحديد سبب حالتك بدقّة

  1. كرّر المشكلة بحساب المستخدم الفعلي وسجّل: رقم الفاتورة بالضبط + ماذا يظهر؟ (لا يوجد زر / «سدّد الفاتورة أولاً» / رسالة الخزنة / صفحة خطأ / لا شيء عند الضغط على «نعم»). هذه الملاحظة الواحدة تحسم السبب.
  2. تأكّد من إعدادات مجموعة صلاحيات هذا المستخدم تحديداً:
    SELECT allowEditDelInBills, noBillEditIfTheirIsPayments, billDeletePassword FROM usergroup WHERE usergroupid = <مجموعته>;
  3. لو الزر يختفي للفواتير غير المسدّدة ← السبب إعداد noBillEditTillPay أو عدم تطابق الدفع.

تحسينات مقترحة للكود (عند السماح بالتعديل لاحقاً)

خطة العمل المختصرة

#الإجراءالخاص بـالأولوية
1افتح بطاقة المنتج 8 وألغِ علامة «خدمة» واحفظمشكلة المخزونعاجل
2اعمل جرد فعلي للمنتج 8 ثم تسوية مخزن لضبط الرصيدمشكلة المخزونعاجل
3راجع المنتجات الأخرى المعدّلة حول 23 ينايروقائيمتوسطة
4عند ظهور «الحذف لا يعمل»: سجّل رقم الفاتورة والرسالة الظاهرةمشكلة الحذفعند الحدوث
5إصلاح رسالة «الخزنة» المُضلِّلة + مسار النسخ الاحتياطيمشكلة الحذفتحسين