SBO_SP_TransactionNotification: Beispiele für SQL Server und HANA

Von Zain Abbas, Kodenova · Aktualisiert am

Kurz gesagtSBO_SP_TransactionNotification ist eine Stored Procedure, die SAP Business One innerhalb derselben Datenbanktransaktion aufruft, sobald ein Objekt angelegt, geändert, gelöscht, storniert oder abgeschlossen wird. Setzt Ihr Code error auf einen anderen Wert als 0, macht SAP B1 die Transaktion rückgängig und zeigt dem Benutzer Ihre error_message an. So setzen Sie eine Geschäftsregel am einfachsten auf allen Kanälen gleichzeitig durch: im B1-Client, in DI-API-Add-ons, im Service Layer und bei Datenimporten.

Im Folgenden sehen Sie, wie die Prozedur aufgebaut ist, dieselben Regeln einmal für SQL Server und einmal für HANA, die Objekttyp-Codes, die Sie wirklich brauchen, und die Fehler, die in bestehenden Prozeduren am häufigsten auftauchen.

Wie funktioniert SBO_SP_TransactionNotification?

Jede Mandantendatenbank wird mit der bereits angelegten Prozedur und einem leeren Block „ADD YOUR CODE HERE“ ausgeliefert. SAP B1 übergibt fünf Eingabeparameter:

ParameterTypInhalt
object_typenvarchar(30)Der Objektcode als Text: '17' für einen Kundenauftrag, '2' für einen Geschäftspartner oder der Code eines UDO
transaction_typenchar(1)A Anlegen, U Ändern, D Löschen, C Stornieren, L Abschließen
num_of_cols_in_keyintAnzahl der Schlüsselspalten des Objekts
list_of_key_cols_tab_delnvarchar(255)Namen der Schlüsselspalten, durch Tabulator getrennt (meist DocEntry, CardCode oder ItemCode)
list_of_cols_val_tab_delnvarchar(255)Die Schlüsselwerte, durch Tabulator getrennt, als Text

Am Ende gibt die Prozedur eine Zeile zurück: die Fehlernummer und die Meldung. 0 bedeutet „durchlassen“. Jede andere Zahl blockiert die Transaktion.

Aus „innerhalb derselben Transaktion“ folgen zwei Dinge. Erstens steht der neue Beleg bereits in ORDR, OINV und den Positionstabellen, wenn Ihr Code läuft, Sie können ihn also wie jede gespeicherte Zeile abfragen. Zweitens verlängert alles, was Sie in die Prozedur schreiben, jeden Speichervorgang im Mandanten, und zwar für jeden Objekttyp. Eine langsame Abfrage im Zweig für Kundenaufträge bremst nur dann nichts anderes aus, wenn der Zweig zuerst nach object_type gefiltert wird.

Was bedeuten die Objekttyp-Codes?

object_type ist derselbe Wert, den Sie in der Spalte ObjType jeder Kopftabelle sehen. Diese Codes decken die meisten Regeln ab:

CodeObjektKopftabelle
2GeschäftspartnerOCRD
4ArtikelOITM
13AusgangsrechnungOINV
14AusgangsgutschriftORIN
15LieferungODLN
17KundenauftragORDR
18EingangsrechnungOPCH
20Wareneingang zur BestellungOPDN
22BestellungOPOR
23VerkaufsangebotOQUT
24ZahlungseingangORCT
30JournalbuchungOJDT
46ZahlungsausgangOVPM
59 / 60Wareneingang / WarenausgangOIGN / OIGE
67UmlagerungOWTR
112Entwurf (jede Belegart)ODRF
202ProduktionsauftragOWOR

Bei einem UDO ist object_type der Code, unter dem Sie das UDO registriert haben, keine Zahl. Details auf Feldebene zu den wichtigsten Tabellen finden Sie in unserer SAP-B1-Tabellenreferenz.

Wie blockieren Sie einen Kundenauftrag ohne Bestellnummer des Kunden?

Eine typische erste Regel: Der Kundenauftrag muss die Referenz des Kunden enthalten (NumAtCard, im Client als Kundenreferenznummer angezeigt, im englischen Client „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;

Die HANA-Version entsteht nicht durch Suchen und Ersetzen aus der T-SQL-Version. Vier Unterschiede sind entscheidend:

  1. Deklarationen stehen am Anfang. SQLScript erwartet cnt INT; im Deklarationsteil vor der ersten Anweisung, dort, wo error und error_message bereits deklariert sind. Eine Deklaration mitten im Rumpf schlägt beim Kompilieren fehl. Alternativ können Sie die Regel in einen eigenen BEGIN ... END-Block mit eigenem DECLARE einschließen.
  2. Kein IF EXISTS (SELECT ...). Zählen Sie in eine Variable und prüfen Sie die Variable.
  3. Parameter lesen Sie mit Doppelpunkt (:object_type), zugewiesen wird mit :=.
  4. Namen in gemischter Schreibweise in Anführungszeichen setzen. "DocEntry" und "NumAtCard" brauchen Anführungszeichen, weil HANA Namen ohne Anführungszeichen als Großbuchstaben behandelt.

Die übrigen Syntaxänderungen (ISNULL zu IFNULL, GETDATE zu CURRENT_DATE, + zu ||) sind dieselben wie bei gespeicherten Abfragen. Unser SQL-zu-HANA-Konverter übernimmt sie für einen schnellen ersten Durchgang, und der Leitfaden zur HANA-Migration behandelt das vollständige Umschreiben der Prozedur.

Wie prüfen Sie Belegpositionen?

Prüfungen im Belegkopf sind einfach, weil der Schlüssel der Kopf ist. Für Positionen verknüpfen Sie die Positionstabelle über DocEntry. Beispiel: Eine Ausgangsrechnung wird blockiert, wenn eine Position den Preis null hat, obwohl der Artikel ein Verkaufsartikel und kein Gratisartikel ist.

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;

Nehmen Sie nach Möglichkeit die Zeilennummer in die Meldung auf ('Line ' + CAST(T1.LineNum + 1 AS NVARCHAR) auf SQL Server, 'Line ' || TO_NVARCHAR(T1."LineNum" + 1) auf HANA). LineNum beginnt bei 0, der Client zählt Zeilen ab 1, und ohne diese Angabe suchen Anwender die richtige Zeile in einer Rechnung mit 60 Positionen.

Wie machen Sie die Steuernummer für die E-Rechnung zur Pflicht?

In Projekten für ZATCA, SRI, FEL und DTE ist eine fehlende oder fehlerhafte Steuernummer des Käufers ein häufiger Grund dafür, dass eine Rechnung abgelehnt wird, nachdem sie in SAP B1 bereits gebucht ist. Den Fehler beim Geschäftspartner abzufangen, ist günstiger, als später eine abgelehnte Rechnung zu korrigieren.

-- 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

Beachten Sie, dass der Schlüssel des Geschäftspartners CardCode ist, ein String, deshalb gibt es hier keinen Cast. In HANA verwendet derselbe Block "CardCode" = :list_of_cols_val_tab_del und das Zählmuster von oben.

Formatregeln (Prüfziffern, Länge je nach Art der Kennung) sind in einem Add-on oder einem Validator vor der Datenerfassung besser aufgehoben, weil sie sich von Land zu Land unterscheiden. Um die Kunden zu finden, die heute an der Regel scheitern würden, führen Sie zuerst die Abfrage für Geschäftspartner ohne Steuernummer aus, bereinigen die Daten und schalten die Regel erst dann ein. Andernfalls wird die erste Person blockiert, die einen alten Kundenstamm bearbeitet, und zwar für etwas, das sie gar nicht verursacht hat.

Welche Fehler passieren häufig?

Regeln, die Entwürfe übergehen. Macht ein Genehmigungsverfahren aus einem Kundenauftrag einen Entwurf, speichert SAP B1 ihn als Objekttyp 112 in ODRF/DRF1, nicht als 17. Ihre Regel für 17 greift erst, wenn der Entwurf in einen echten Beleg umgewandelt wird. Oft ist das in Ordnung. Soll die Regel fehlerhafte Daten aber stoppen, bevor ein Genehmiger sie sieht, ergänzen Sie einen Zweig für 112, gefiltert auf ODRF.ObjType = '17'.

Zusätzliche Ergebnismengen zurückgeben. Auf SQL Server kann ein vergessenes SELECT oder PRINT zum Debuggen Ergebnismengen vor dem abschließenden SELECT @error, @error_message erzeugen, und SAP B1 liest dann womöglich die falsche. Halten Sie Debug-Ausgaben aus der Prozedur heraus; wenn nötig, protokollieren Sie in eine Tabelle.

Langsame Zweige. Jeder Speichervorgang im Mandanten wartet auf die Prozedur. Filtern Sie im äußeren IF nach object_type und transaction_type, verwenden Sie den Schlüssel in der WHERE-Klausel und verzichten Sie auf Cursor. Eine Regel, die OINV ohne Filter auf DocEntry durchsucht, wirkt im Testmandanten harmlos und wird in einer Datenbank mit Rechnungen aus vielen Jahren zur Qual.

Fehlercodes, die niemand zuordnen kann. Verwenden Sie ein Nummernschema, zum Beispiel Objekttyp mal 1000 plus Zähler (17001, 13001, 2001). Schickt ein Anwender einen Screenshot, wissen Sie sofort, welcher Zweig gegriffen hat, ohne die ganze Prozedur zu lesen.

Stornieren und Abschließen sind eigene Ereignisse. C und L kommen als eigene Transaktionstypen an. Eine Regel für A und U hindert niemanden daran, einen Kundenauftrag abzuschließen. Wenn Sie das Abschließen blockieren müssen (etwa weil noch offene Lieferungen verknüpft sind), behandeln Sie L ausdrücklich.

Dinge blockieren, die SAP B1 im Hintergrund aktualisiert. Manche Objekte werden als Nebeneffekt einer anderen Transaktion geändert (die Bestandsfelder eines Artikels beim Buchen eines Belegs, der Saldo eines Geschäftspartners). Eine strenge Regel auf U für Objekt 4 oder 2 kann deshalb einen Verkaufsbeleg blockieren, nur wegen einer solchen Nebenänderung. Halten Sie Änderungsregeln eng gefasst und testen Sie sie, indem Sie Belege buchen, nicht nur, indem Sie Stammdaten bearbeiten.

Code bei einem Neuaufbau verlieren. Die Prozedur liegt in der Mandantendatenbank. Bewahren Sie das vollständige Skript in der Versionsverwaltung auf und vergleichen Sie es nach Upgrades, Mandantenkopien und Wiederherstellungen, damit keine Regel unbemerkt verschwindet.

Und was ist mit SBO_SP_PostTransactionNotice?

Sie hat dieselben Parameter, aber SAP B1 ruft sie erst auf, nachdem die Transaktion festgeschrieben wurde. Blockieren kann sie nichts. Nutzen Sie sie für Nebenaufgaben: in eine Protokolltabelle schreiben oder den DocEntry in eine Warteschlange für eine Schnittstelle stellen (einen Versanddienst für E-Rechnungen, eine WhatsApp-Benachrichtigung, eine Synchronisation mit einem anderen System). Halten Sie auch sie schlank und rufen Sie aus ihr heraus niemals externe Dienste auf. Eine Warteschlangentabelle plus ein separater Dienst, der sie ausliest, ist das sicherere Design.

Wie sehen Add-ons und der Service Layer den Fehler?

Die Regel läuft auf Datenbankebene und gilt daher unabhängig davon, woher der Beleg kommt. Ein Add() über die DI API liefert ein Ergebnis ungleich null, und Ihre Meldung kommt über GetLastError zurück. Ein POST an den Service Layer schlägt mit einem Fehler fehl, der den Meldungstext enthält (wie Sie ihn auslesen, steht in der Anleitung zum Service Layer). Importe mit der Data Transfer Workbench scheitern Zeile für Zeile. Deshalb ist die Prozedur der richtige Ort für Regeln, die überall gelten müssen, und deshalb sollten Ihre Meldungen auch für jemanden verständlich sein, der ein Schnittstellenprotokoll liest, nicht nur für einen Anwender im Client. Zur Fehlerbehandlung auf Seiten der DI API lesen Sie unseren Leitfaden zu DI-API-Fehlercodes.


Sie brauchen solche Regeln, geschrieben und getestet für SQL Server und HANA? Das gehört zu unseren Leistungen in der Entwicklung für SAP Business One.

Quellen

Passende Leistungen

FAQ

Häufige Fragen

Wo finde ich SBO_SP_TransactionNotification?

Auf SQL Server liegt sie in der Mandantendatenbank unter Programmierbarkeit > Gespeicherte Prozeduren. Auf HANA finden Sie sie im Schema des Mandanten unter Procedures. Bearbeiten Sie sie mit SSMS oder SAP HANA Studio bzw. dem Database Explorer und testen Sie zuerst in einer Kopie des Mandanten.

Kann ich mehrere Regeln für dasselbe Objekt anlegen?

Ja. Fügen Sie mehrere IF-Blöcke im selben Zweig hinzu. Prüfen Sie error, bevor der nächste Block läuft, sonst überschreibt die letzte fehlschlagende Regel die erste Meldung.

Ist TransactionNotification dasselbe wie ein Genehmigungsverfahren?

Nein. Ein Genehmigungsverfahren leitet einen Beleg zur Entscheidung an eine Person weiter und lässt ihn nach der Freigabe durch. TransactionNotification weist den Beleg einfach so lange zurück, bis die Daten stimmen. Nutzen Sie Genehmigungen für Ermessensentscheidungen und die Prozedur für feste Regeln.

Bereit für den Start?

Sagen Sie uns, was Sie brauchen. Sie erhalten eine klare technische Antwort, einen festen Leistungsumfang und einen Preis, in der Regel innerhalb von 24 Stunden.