SBO_SP_TransactionNotification: أمثلة لـ SQL Server و HANA
بقلم زين عباس, كودينوفا · آخر تحديث
فيما يلي طريقة ربط الإجراء، والقواعد نفسها مكتوبة لـ SQL Server ولـ HANA، ورموز أنواع الكائنات التي تحتاجها فعلًا، والأخطاء التي تظهر أكثر من غيرها في الإجراءات القائمة.
كيف يعمل SBO_SP_TransactionNotification؟
تأتي كل قاعدة بيانات شركة والإجراء منشأ فيها مسبقًا مع كتلة فارغة بعنوان “ADD YOUR CODE HERE”. يمرر SAP B1 خمسة مدخلات:
| المعامل | النوع | ما الذي يحمله |
|---|---|---|
object_type | nvarchar(30) | رمز الكائن كنص: '17' لأمر البيع، و'2' لشريك الأعمال، أو رمز كائن UDO |
transaction_type | nchar(1) | A إضافة، U تحديث، D حذف، C إلغاء، L إغلاق |
num_of_cols_in_key | int | عدد أعمدة المفتاح في الكائن |
list_of_key_cols_tab_del | nvarchar(255) | أسماء أعمدة المفتاح مفصولة بعلامة Tab (عادةً DocEntry أو CardCode أو ItemCode) |
list_of_cols_val_tab_del | nvarchar(255) | قيم المفتاح مفصولة بعلامة Tab، كنص |
في النهاية يعيد الإجراء صفًا واحدًا: رقم الخطأ والرسالة. القيمة 0 تعني «اسمح بالمرور». وأي رقم آخر يمنع الحركة.
ينتج أمران عن عبارة «داخل الحركة نفسها». الأول: عند تشغيل كودك يكون المستند الجديد موجودًا بالفعل في ORDR و OINV وجداول الأسطر، فيمكنك الاستعلام عنه كأي صف مُثبَّت. والثاني: كل ما تضعه في الإجراء يضيف وقتًا إلى كل عملية حفظ في الشركة، لكل أنواع الكائنات. والاستعلام البطيء في فرع أمر البيع لا يبطئ غيره فقط إذا صُفّي الفرع حسب object_type أولًا.
ماذا تعني رموز أنواع الكائنات؟
object_type هي القيمة نفسها التي تراها في عمود ObjType في كل جدول رأس. هذه الرموز تغطي معظم القواعد:
| الرمز | الكائن | جدول الرأس |
|---|---|---|
| 2 | شريك أعمال | OCRD |
| 4 | صنف | OITM |
| 13 | فاتورة مبيعات | OINV |
| 14 | إشعار دائن للمبيعات | ORIN |
| 15 | مذكرة تسليم | ODLN |
| 17 | أمر بيع | ORDR |
| 18 | فاتورة مشتريات | OPCH |
| 20 | إيصال استلام بضاعة لأمر الشراء | OPDN |
| 22 | أمر شراء | OPOR |
| 23 | عرض أسعار مبيعات | OQUT |
| 24 | دفعة واردة | ORCT |
| 30 | قيد يومية | OJDT |
| 46 | دفعة صادرة | OVPM |
| 59 / 60 | استلام بضاعة / صرف بضاعة | OIGN / OIGE |
| 67 | تحويل مخزون | OWTR |
| 112 | مسودة (أي نوع مستند) | ODRF |
| 202 | أمر إنتاج | OWOR |
بالنسبة لكائن UDO، تكون object_type هي رمز UDO الذي سجّلته، لا رقمًا. وتفاصيل الحقول للجداول الرئيسية موجودة في مرجع جداول SAP B1 لدينا.
كيف تمنع أمر بيع بلا رقم أمر شراء العميل؟
قاعدة أولى نموذجية: يجب أن يحمل أمر البيع مرجع العميل (NumAtCard، ويظهر في العميل باسم “Customer Ref. No.”).
SQL Server (T-SQL):
-- inside the "ADD YOUR CODE HERE" block
IF @object_type = '17' AND @transaction_type IN ('A', 'U')
BEGIN
IF EXISTS (SELECT 1
FROM ORDR
WHERE DocEntry = CAST(@list_of_cols_val_tab_del AS INT)
AND ISNULL(NumAtCard, '') = '')
BEGIN
SET @error = 17001
SET @error_message = N'Sales order: enter the Customer Ref. No.'
END
END
SAP HANA (SQLScript):
-- declare at the top of the procedure, next to error and error_message:
-- cnt INT;
IF :object_type = '17' AND (:transaction_type = 'A' OR :transaction_type = 'U') THEN
SELECT COUNT(*) INTO cnt
FROM "ORDR"
WHERE "DocEntry" = CAST(:list_of_cols_val_tab_del AS INT)
AND IFNULL("NumAtCard", '') = '';
IF :cnt > 0 THEN
error := 17001;
error_message := N'Sales order: enter the Customer Ref. No.';
END IF;
END IF;
نسخة HANA ليست بحثًا واستبدالًا لنسخة T-SQL. أربعة فروق مهمة:
- التعريفات في الأعلى. يريد SQLScript السطر
cnt INT;في قسم التعريفات قبل أول جملة، حيثerrorوerror_messageمعرّفان أصلًا. تعريفه في وسط الجسم يفشل عند الترجمة. وإن شئت، ضع القاعدة في كتلةBEGIN ... ENDخاصة بها معDECLAREخاص بها. - لا يوجد
IF EXISTS (SELECT ...). احسب العدد في متغير ثم اختبر المتغير. - اقرأ المعاملات بنقطتين (
:object_type)، وأسند القيم بـ:=. - اقتبس الأسماء ذات الأحرف المختلطة. يجب اقتباس
"DocEntry"و"NumAtCard"لأن HANA يعامل الأسماء غير المقتبسة كأحرف كبيرة.
استبدالات الصياغة الأخرى (ISNULL إلى IFNULL، و GETDATE إلى CURRENT_DATE، و + إلى ||) هي نفسها التي تجريها في الاستعلامات المحفوظة. يتعامل محوّل SQL إلى HANA لدينا معها في مرور أول سريع، ويغطي دليل الترحيل إلى HANA إعادة كتابة الإجراء كاملة.
كيف تتحقق من أسطر المستند؟
فحوص الرأس سهلة لأن المفتاح هو الرأس. أما للأسطر فاربط جدول الأسطر عبر DocEntry. مثال: امنع فاتورة مبيعات فيها أي سطر بسعر صفر لصنف يُباع وليس مجانيًا.
SQL Server:
IF @object_type = '13' AND @transaction_type = 'A'
BEGIN
IF EXISTS (SELECT 1
FROM INV1 T1
INNER JOIN OITM T2 ON T2.ItemCode = T1.ItemCode
WHERE T1.DocEntry = CAST(@list_of_cols_val_tab_del AS INT)
AND T1.Price = 0
AND T2.SellItem = 'Y')
BEGIN
SET @error = 13001
SET @error_message = N'A/R invoice: a line has price 0. Check the price list.'
END
END
SAP HANA:
IF :object_type = '13' AND :transaction_type = 'A' THEN
SELECT COUNT(*) INTO cnt
FROM "INV1" T1
INNER JOIN "OITM" T2 ON T2."ItemCode" = T1."ItemCode"
WHERE T1."DocEntry" = CAST(:list_of_cols_val_tab_del AS INT)
AND T1."Price" = 0
AND T2."SellItem" = 'Y';
IF :cnt > 0 THEN
error := 13001;
error_message := N'A/R invoice: a line has price 0. Check the price list.';
END IF;
END IF;
ضع رقم السطر في الرسالة متى استطعت ('Line ' + CAST(T1.LineNum + 1 AS NVARCHAR) في SQL Server، و'Line ' || TO_NVARCHAR(T1."LineNum" + 1) في HANA). يبدأ LineNum من 0 بينما يعرض العميل الصفوف بدءًا من 1، وإلا سيبحث المستخدمون في فاتورة من 60 سطرًا.
كيف تجعل الرقم الضريبي إلزاميًا للفوترة الإلكترونية؟
في مشاريع ZATCA و SRI و FEL و DTE، يُعد غياب الرقم الضريبي للمشتري أو خطأ صيغته سببًا شائعًا لرفض الفاتورة بعد أن تكون قد رُحّلت بالفعل في SAP B1. إيقاف ذلك عند شريك الأعمال أرخص من إصلاح فاتورة مرفوضة لاحقًا.
-- SQL Server: customers must have a tax ID (LicTradNum)
IF @object_type = '2' AND @transaction_type IN ('A', 'U')
BEGIN
IF EXISTS (SELECT 1 FROM OCRD
WHERE CardCode = @list_of_cols_val_tab_del
AND CardType = 'C'
AND ISNULL(LicTradNum, '') = '')
BEGIN
SET @error = 2001
SET @error_message = N'Customer: Federal Tax ID is required for e-invoicing.'
END
END
لاحظ أن مفتاح شريك الأعمال هو CardCode، وهو نص، فلا يوجد تحويل نوع (CAST) هنا. وفي HANA تستخدم الكتلة نفسها "CardCode" = :list_of_cols_val_tab_del مع نمط العدّ السابق.
قواعد الصيغة (أرقام التحقق، والطول حسب نوع الرقم) من الأفضل تنفيذها في إضافة أو أداة تحقق قبل إدخال البيانات، لأنها تختلف من دولة لأخرى. لمعرفة العملاء الذين سيفشلون اليوم، شغّل أولًا استعلام شركاء الأعمال بلا رقم ضريبي، ونظّف بياناتهم، ثم فعّل القاعدة. وإلا فإن أول شخص يعدّل سجل عميل قديم سيُمنع بسبب شيء لم يتسبب فيه.
ما الأخطاء الشائعة؟
قواعد تتجاهل المسودات. إذا حوّل إجراء موافقة أمر بيع إلى مسودة، يحفظه SAP B1 ككائن من النوع 112 في ODRF/DRF1، لا كـ 17. قاعدتك للنوع 17 لا تعمل إلا عند تحويل المسودة إلى مستند حقيقي. وهذا مقبول غالبًا، لكن إذا كان الهدف من القاعدة إيقاف البيانات الخاطئة قبل أن يراها المعتمِد، فأضف فرعًا للنوع 112 مصفّى بالشرط ODRF.ObjType = '17'.
إرجاع مجموعات نتائج إضافية. في SQL Server، قد تنتج جملة SELECT أو PRINT شاردة مستخدمة لتصحيح الأخطاء مجموعات نتائج قبل جملة SELECT @error, @error_message النهائية، وقد يقرأ SAP B1 المجموعة الخاطئة. أبقِ مخرجات التصحيح خارج الإجراء؛ وسجّلها في جدول إن اضطررت.
الفروع البطيئة. كل عملية حفظ في الشركة تنتظر الإجراء. صفِّ حسب object_type و transaction_type في جملة IF الخارجية، واستخدم المفتاح في جملة WHERE، وتجنب المؤشرات. القاعدة التي تمسح OINV دون شرط DocEntry ستبدو جيدة في شركة اختبار ومؤلمة في قاعدة بيانات فيها فواتير سنوات.
رموز أخطاء لا يمكن تتبعها. استخدم نظام ترقيم، مثل نوع الكائن مضروبًا في 1000 مع عدّاد (17001 و13001 و2001). عندما يرسل مستخدم لقطة شاشة، تعرف أي فرع عمل دون قراءة الإجراء كله.
الإلغاء والإغلاق حدثان منفصلان. يصل C و L كنوعي حركة مستقلين. القاعدة المكتوبة لـ A و U لن تمنع أحدًا من إغلاق أمر بيع. إذا كنت تحتاج منع الإغلاق (مثلًا عندما تكون هناك مذكرات تسليم مفتوحة مرتبطة)، فتعامل مع L صراحة.
منع أشياء يحدّثها SAP B1 في الخلفية. تُحدَّث بعض الكائنات كأثر جانبي لحركة أخرى (حقول المخزون في الصنف عند ترحيل مستند، ورصيد شريك الأعمال). قاعدة صارمة على U للكائن 4 أو 2 قد تمنع مستند مبيعات بسبب تحديث جانبي. اجعل قواعد التحديث ضيقة، واختبرها بترحيل مستندات، لا بتعديل البيانات الرئيسية فقط.
فقدان الكود عند إعادة البناء. يعيش الإجراء في قاعدة بيانات الشركة. احتفظ بالسكربت الكامل في نظام إدارة الكود المصدري وقارنه بعد الترقيات ونسخ الشركات وعمليات الاستعادة، حتى لا تختفي قاعدة بصمت أبدًا.
وماذا عن SBO_SP_PostTransactionNotice؟
له المعاملات نفسها، لكن SAP B1 يستدعيه بعد تثبيت الحركة. ولا يستطيع منع أي شيء. استخدمه للمهام الجانبية: الكتابة في جدول سجل، أو وضع DocEntry في طابور لتكامل ما (مُرسِل الفوترة الإلكترونية، أو إشعار واتساب، أو مزامنة مع نظام آخر). اجعله خفيفًا أيضًا، ولا تستدعِ خدمات خارجية من داخله أبدًا. جدول طابور مع خدمة منفصلة تقرؤه هو التصميم الأكثر أمانًا.
كيف ترى الإضافات و Service Layer الخطأ؟
تعمل القاعدة على مستوى قاعدة البيانات، فتنطبق أيًّا كان مصدر المستند. تعيد الدالة Add() في DI API نتيجة غير صفرية، وتعود رسالتك عبر GetLastError. ويفشل طلب POST في Service Layer بخطأ يحمل نص الرسالة (راجع دليل Service Layer لمعرفة كيفية قراءته). وتفشل عمليات الاستيراد في Data Transfer Workbench صفًا بصف. لهذا يُعد الإجراء مكانًا جيدًا للقواعد التي يجب أن تسري في كل مكان، ولهذا يجب أن تكون رسائلك مفهومة لمن يقرأ سجل تكامل، لا لمستخدم العميل فقط. ولجانب DI API من معالجة الأخطاء، راجع دليل رموز أخطاء DI API لدينا.
تحتاج قواعد كهذه مكتوبة ومختبرة على SQL Server و HANA معًا؟ هذا جزء من عملنا في تطوير SAP Business One.
المصادر
- مجتمع SAP: تطبيق SBO_SP_TransactionNotification في SAP Business One، إصدار SAP HANA (2013)
- بوابة SAP Help: SAP Business One
خدمات ذات صلة
أسئلة شائعة
أين أجد SBO_SP_TransactionNotification؟
في SQL Server يوجد تحت قاعدة بيانات الشركة في Programmability > Stored Procedures. وفي HANA يوجد في مخطط الشركة تحت Procedures. عدّله باستخدام SSMS أو SAP HANA Studio / Database Explorer، واختبره أولًا على نسخة من الشركة.
هل يمكن أن يكون لدي أكثر من قاعدة للكائن نفسه؟
نعم. أضف عدة كتل IF داخل الفرع نفسه. افحص قيمة error قبل تشغيل الكتلة التالية، وإلا فإن آخر قاعدة فاشلة تكتب فوق الرسالة الأولى.
هل TransactionNotification هو نفسه إجراء الموافقة؟
لا. يوجّه إجراء الموافقة المستند إلى شخص ليتخذ قرارًا ويسمح بمروره بعد الموافقة. أما TransactionNotification فيرفض المستند ببساطة حتى تصبح البيانات صحيحة. استخدم الموافقات للقرارات التقديرية والإجراء للقواعد الصارمة.
جاهز للبدء؟
أخبرنا بما تحتاجه. ستحصل على إجابة تقنية واضحة ونطاق عمل محدد وسعر ثابت، عادةً خلال 24 ساعة.
