SBO_SP_TransactionNotification: esempi per SQL Server e HANA
By Zain Abbas, Kodenova · Updated
Di seguito vedrà come è collegata la procedura, le stesse regole scritte per SQL Server e per HANA, i codici del tipo di oggetto che servono davvero e gli errori che compaiono più spesso nelle procedure esistenti.
Come funziona SBO_SP_TransactionNotification?
Ogni database aziendale contiene già la procedura, con un blocco vuoto “ADD YOUR CODE HERE”. SAP B1 passa cinque parametri di input:
| Parametro | Tipo | Cosa contiene |
|---|---|---|
object_type | nvarchar(30) | Il codice dell’oggetto come testo: '17' per un ordine cliente, '2' per un business partner, oppure il codice di un UDO |
transaction_type | nchar(1) | A aggiunta, U aggiornamento, D eliminazione, C annullamento, L chiusura |
num_of_cols_in_key | int | Quante colonne chiave ha l’oggetto |
list_of_key_cols_tab_del | nvarchar(255) | I nomi delle colonne chiave, separati da tabulazione (di solito DocEntry, CardCode o ItemCode) |
list_of_cols_val_tab_del | nvarchar(255) | I valori chiave, separati da tabulazione, come testo |
Alla fine la procedura restituisce una riga: il numero di errore e il messaggio. 0 significa “lascia passare”. Qualsiasi altro numero blocca la transazione.
Dal fatto che la procedura giri “nella stessa transazione” derivano due conseguenze. Primo: quando il suo codice viene eseguito, il nuovo documento si trova già in ORDR, OINV e nelle tabelle di riga, quindi può interrogarlo come qualsiasi riga già registrata. Secondo: tutto ciò che inserisce nella procedura allunga ogni salvataggio della società, per ogni tipo di oggetto. Una query lenta nel ramo degli ordini cliente non rallenta nient’altro solo se il ramo viene prima filtrato per object_type.
Che cosa significano i codici del tipo di oggetto?
object_type è lo stesso valore che compare nella colonna ObjType di ogni tabella di testata. Quelli che coprono la maggior parte delle regole:
| Codice | Oggetto | Tabella di testata |
|---|---|---|
| 2 | Business partner | OCRD |
| 4 | Articolo | OITM |
| 13 | Fattura clienti | OINV |
| 14 | Nota di credito clienti | ORIN |
| 15 | Consegna | ODLN |
| 17 | Ordine cliente | ORDR |
| 18 | Fattura fornitori | OPCH |
| 20 | Entrata merci da ordine d’acquisto | OPDN |
| 22 | Ordine d’acquisto | OPOR |
| 23 | Offerta di vendita | OQUT |
| 24 | Incasso | ORCT |
| 30 | Registrazione contabile | OJDT |
| 46 | Pagamento in uscita | OVPM |
| 59 / 60 | Entrata merci / uscita merci | OIGN / OIGE |
| 67 | Trasferimento di magazzino | OWTR |
| 112 | Bozza (qualsiasi tipo di documento) | ODRF |
| 202 | Ordine di produzione | OWOR |
Per un UDO, object_type è il codice UDO che ha registrato, non un numero. I dettagli a livello di campo per le tabelle principali sono nel nostro riferimento alle tabelle SAP B1.
Come si blocca un ordine cliente senza il numero d’ordine del cliente?
Una tipica prima regola: l’ordine cliente deve riportare il riferimento del cliente (NumAtCard, mostrato nel client come “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;
La versione HANA non è un semplice “trova e sostituisci” di quella T-SQL. Contano quattro differenze:
- Le dichiarazioni vanno all’inizio. SQLScript richiede
cnt INT;nella sezione delle dichiarazioni, prima della prima istruzione, dove sono già dichiaratierrorederror_message. Una dichiarazione a metà del corpo genera un errore di compilazione. Se preferisce, racchiuda la regola in un proprio bloccoBEGIN ... ENDcon il suoDECLARE. - Niente
IF EXISTS (SELECT ...). Conti in una variabile e verifichi la variabile. - I parametri si leggono con i due punti (
:object_type) e si assegnano con:=. - I nomi con maiuscole e minuscole vanno tra virgolette.
"DocEntry"e"NumAtCard"devono essere racchiusi tra virgolette, perché HANA tratta i nomi senza virgolette come maiuscoli.
Le altre sostituzioni di sintassi (da ISNULL a IFNULL, da GETDATE a CURRENT_DATE, da + a ||) sono le stesse che si fanno nelle query salvate. Il nostro convertitore da SQL a HANA le gestisce per un primo passaggio rapido, e la guida alla migrazione a HANA copre la riscrittura completa della procedura.
Come si convalidano le righe del documento?
I controlli sulla testata sono semplici, perché la chiave è la testata. Per le righe, unisca la tabella di riga su DocEntry. Esempio: bloccare una fattura clienti che ha una riga con prezzo zero per un articolo destinato alla vendita, non omaggio.
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;
Quando possibile, inserisca il numero di riga nel messaggio ('Line ' + CAST(T1.LineNum + 1 AS NVARCHAR) su SQL Server, 'Line ' || TO_NVARCHAR(T1."LineNum" + 1) su HANA). LineNum parte da 0, il client mostra le righe a partire da 1, e altrimenti gli utenti dovranno cercare la riga in una fattura di 60 righe.
Come si rende obbligatorio l’identificativo fiscale per la fatturazione elettronica?
Nei progetti ZATCA, SRI, FEL e DTE, un identificativo fiscale dell’acquirente mancante o errato è un motivo frequente di scarto di una fattura già registrata in SAP B1. Fermare l’errore sul business partner costa meno che correggere in seguito una fattura scartata. Lo stesso vale in Italia, dove lo SdI controlla la partita IVA o il codice fiscale del cliente prima del recapito: veda SAP Business One in Italia.
-- 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
Noti che la chiave del business partner è CardCode, una stringa, quindi qui non serve alcuna conversione. In HANA lo stesso blocco usa "CardCode" = :list_of_cols_val_tab_del e lo schema con il conteggio visto sopra.
Le regole di formato (cifre di controllo, lunghezza per tipo di identificativo) è meglio gestirle in un add-on o in un validatore prima dell’inserimento dei dati, perché cambiano da paese a paese. Per trovare i clienti che oggi non supererebbero il controllo, esegua prima la query dei business partner senza identificativo fiscale, li sistemi e solo dopo attivi la regola. Altrimenti la prima persona che modifica una vecchia anagrafica cliente viene bloccata per qualcosa che non ha causato.
Quali sono gli errori più comuni?
Regole che ignorano le bozze. Se una procedura di autorizzazione trasforma un ordine cliente in bozza, SAP B1 lo salva come tipo di oggetto 112 in ODRF/DRF1, non come 17. La sua regola per 17 scatta solo quando la bozza viene convertita in un documento reale. Spesso va bene così, ma se la regola deve fermare i dati errati prima che li veda chi approva, aggiunga un ramo 112 filtrato su ODRF.ObjType = '17'.
Result set aggiuntivi. Su SQL Server, una SELECT o una PRINT dimenticata per il debug può produrre result set prima della SELECT @error, @error_message finale, e SAP B1 potrebbe leggere quello sbagliato. Tenga l’output di debug fuori dalla procedura; se proprio serve, scriva in una tabella di log.
Rami lenti. Ogni salvataggio della società attende la procedura. Filtri per object_type e transaction_type nell’IF esterno, usi la chiave nella clausola WHERE ed eviti i cursori. Una regola che scorre OINV senza il filtro su DocEntry sembrerà a posto in una società di test e diventerà pesante in un database con anni di fatture.
Codici di errore impossibili da rintracciare. Usi uno schema di numerazione, per esempio il tipo di oggetto per 1000 più un contatore (17001, 13001, 2001). Quando un utente invia uno screenshot, saprà quale ramo è scattato senza leggere l’intera procedura.
Annullamento e chiusura sono eventi distinti. C e L arrivano come tipi di transazione a sé. Una regola scritta per A e U non impedisce a nessuno di chiudere un ordine cliente. Se deve bloccare una chiusura (per esempio quando ci sono ancora consegne aperte collegate), gestisca L in modo esplicito.
Bloccare ciò che SAP B1 aggiorna in background. Alcuni oggetti vengono aggiornati come effetto collaterale di un’altra transazione (i campi di giacenza di un articolo quando si registra un documento, il saldo di un business partner). Una regola rigida su U per l’oggetto 4 o 2 può bloccare un documento di vendita a causa di un aggiornamento collaterale. Mantenga ristrette le regole sugli aggiornamenti e le provi registrando documenti, non solo modificando le anagrafiche.
Perdere il codice in una ricostruzione. La procedura risiede nel database aziendale. Conservi lo script completo in un sistema di controllo versione e lo confronti dopo aggiornamenti, copie di società e ripristini, così che nessuna regola sparisca senza che nessuno se ne accorga.
E SBO_SP_PostTransactionNotice?
Ha gli stessi parametri, ma SAP B1 la richiama dopo il commit della transazione. Non può bloccare nulla. La usi per attività accessorie: scrivere in una tabella di log o mettere il DocEntry in una coda per un’integrazione (un invio di fatture elettroniche, una notifica WhatsApp, una sincronizzazione con un altro sistema). Anche questa va mantenuta leggera, e non deve mai richiamare servizi esterni al suo interno. Una tabella di coda più un servizio separato che la legge è la soluzione più sicura.
Come vedono l’errore gli add-on e il Service Layer?
La regola gira a livello di database, quindi si applica indipendentemente dalla provenienza del documento. Un Add() della DI API restituisce un risultato diverso da zero e il suo messaggio torna tramite GetLastError. Un POST al Service Layer fallisce con un errore che contiene il testo del messaggio (veda la guida al Service Layer per sapere come leggerlo). Le importazioni con Data Transfer Workbench falliscono riga per riga. Per questo la procedura è il posto giusto per le regole che devono valere ovunque, e per questo i suoi messaggi devono essere comprensibili a chi legge il log di un’integrazione, non solo a un utente nel client. Per la gestione degli errori lato DI API, veda la nostra guida ai codici di errore della DI API.
Le servono regole come queste, scritte e testate sia su SQL Server sia su HANA? Fa parte del nostro lavoro di sviluppo SAP Business One.
Sources
- SAP Community: Implementing SBO_SP_TransactionNotification of SAP Business One, version for SAP HANA (2013)
- SAP Help Portal: SAP Business One
Related services
Frequently asked questions
Dove si trova SBO_SP_TransactionNotification?
Su SQL Server si trova nel database aziendale, in Programmability > Stored Procedures. Su HANA si trova nello schema aziendale, in Procedures. La modifichi con SSMS oppure con SAP HANA Studio / Database Explorer, e la provi prima in una copia della società.
Posso avere più di una regola per lo stesso oggetto?
Sì. Aggiunga più blocchi IF all'interno dello stesso ramo. Controlli error prima di eseguire il blocco successivo, altrimenti l'ultima regola non superata sovrascrive il primo messaggio.
TransactionNotification equivale a una procedura di autorizzazione?
No. Una procedura di autorizzazione invia un documento a una persona perché decida e lo lascia passare dopo l'approvazione. TransactionNotification si limita a rifiutare il documento finché i dati non sono corretti. Usi le autorizzazioni per le decisioni di merito e la procedura per le regole rigide.
Iniziamo?
Ci descriva di cosa ha bisogno. Riceverà una risposta tecnica chiara, un perimetro definito e un prezzo, di norma entro 24 ore.
