6. April 2022

Überwindung der FSM Form Limitations mit Google Forms

Die intelligenten FSM-Formulare sind immer noch nicht so intelligent, wie viele von uns es gerne hätten. Für Fälle, in denen das Pulldown-Menü vom Wert eines vorhergehenden Formularfeldes abhängt, ist dieses Tool einfach nicht intelligent genug. Zum Glück können einfache und kostenlose Google-Formulare verwendet werden.

Obwohl FSM die Möglichkeit bietet, Smartforms direkt über den systemeigenen Editor zu erstellen, hat diese Methode eine wesentliche Einschränkung, nämlich das Fehlen einer dynamischen Generierung dieser Protokolle auf der Grundlage der eingegebenen Daten. (Mit Ausnahme des Ein- und Ausblendens einiger Abschnitte. Es kann nur auf Daten reagieren, die direkt im Smartform enthalten sind, nicht zum Beispiel auf das Material, das in der Aktivität erstellt wurde.)

Im Falle eines unserer Kunden aus dem Bereich der Telekommunikation haben wir im Rahmen dieses Projekts andere, flexiblere Möglichkeiten zur Erstellung dieser Protokolle geprüft. Als idealste Lösung haben wir uns für die Integration in Google Forms entschieden, welches die notwendigen Abschnitte verarbeitet und dynamisch vervollständigt, das Design des Protokolls bestimmt und als Ergebnis eine PDF-Version des Protokolls zusammen mit dem Versand an den Kunden erzeugt.

Für diese Integration nutzten wir die Geschäftsregel-Funktionalität auf der SAP FSM-Seite, um eine zu erstellen undum die Informationen zu senden und zu formatieren, die erforderlich sind, um das endgültige Protokoll in Google-Formularen zu erstellen, die an den Kunden gesendet werden.

Definition von Geschäftsregeln

Die wichtigste Einstellung bei der Definition dieser Regel ist die Entscheidung für die volle Unterstützung von Javascript-Ausdrücken. Und zwar aus diesen beiden Gründen:

  1. Die Wahl der Option ohne die Unterstützung dieser Ausdrücke ist veraltet.
  2. In den nächsten Abschnitten dieser Regel werden wir aktiv Javascript-Funktionen verwenden müssen, um die an Google Forms gesendeten Daten richtig zu formatieren.

Überwindung der Beschränkungen von Formularen mit Google-Formularen-1

Trigger

Je nach Ihren spezifischen Anforderungen können Sie eine Beliebige auswählen. In unserem Fall haben wir uns aus verschiedenen Gründen (wie Leistungsoptimierung und Datenverfügbarkeit) für die folgende Variante entschieden:
Überwindung der Beschränkungen von Formularen mit Google-Formularen-2

Variablen

In diesem Blog werden wir nicht auf jede einzelne eingehen, sondern uns nur auf diejenigen konzentrieren, die mit den Unterschriften von Technikern und Kunden arbeiten.
Überwindung der Beschränkungen von Formularen mit Google-Formularen-3

Definitionen und Kommentare zu den einzelnen Variablen

Name der Variablen: Elements
Typ der Variablen: Array
CoreSQL-Definition:

SELECT cie.value, cie.elementId, cie.description FROM Checklisteninstanz ci

JOIN ChecklistInstanceElement cie ON ci.id = cie.checklistInstance

WHERE cie.elementId IN (‚z_f_sf_podpisucas‘, ‚z_f_sf_podpistech‘, ‚z_f_sf_typvyjazdu‘, ‚z_f_sf_techpoznamka‘) AND ci.object.objectId = ${activity.id}

DTO: ChecklisteInstanz.18;ChecklisteInstanzElement.17

Kommentar: V rámci zadania od nášho zákazníka bolo nevyhnutné, aby výsledný PDF protokol obsahoval podpisy oboch strán (technika a zákazníka). Pre ich odoslanie do Google Forms, sme tak potrebovali z databázy systému FSM načítať ich dáta v binárnej podobe enkódované, ako base64 reťazec. Pre tento účel najprv vyťahujeme zo smartformuláru (checklistu) ID záznamov týchto obrázkov uložených v objekte Attachment, na základe názvov týchto elementov (‚z_f_sf_podpistech‘ a ‚z_f_sf_podpisucas‘), ktoré sme zadefinovali v rámci Smartformuláru vypĺňaného technikom pri Servise. Pomocou týchto ID následne dotiahneme ich záznamy z objektu Attachment prostredníctom premenných „signatureCustomer“ a „signatureTechnician“:

Als Teil der Anforderung unseres Kunden war es erforderlich, dass das resultierende PDF-Protokoll die Unterschriften beider Parteien (Techniker und Kunde) enthält. Um sie an Google Forms zu senden, mussten wir ihre Daten aus der FSM-Datenbank in binärer Form abrufen, kodiert als base64-String. Zu diesem Zweck extrahieren wir zunächst aus dem Smartform (Checkliste) die Datensatz-IDs dieser Bilder, die im Attachment-Objekt gespeichert sind, und zwar auf der Grundlage der Namen dieser Elemente („z_f_sf_podpistech“ und „z_f_sf_podpisucas“), die wir in dem Smartform definiert haben, das der Techniker, der den Service-Aufruf ausführt, ausgefüllt hat. Anhand dieser IDs holen wir uns dann ihre Datensätze aus dem Attachment-Objekt mit Hilfe der Variablen„signatureCustomer“ und „signatureTechnician“:

Name der Variablen: signatureCustomer
Variablentyp: Objekt
Objekttyp: Attachement
CoreSQL WHERE-Klausel:

signatureCustomer.id = ${elements.filter(function (e) { return e.elementId === ‚z_f_sf_podpisucas‘ })[0].value}

– (Die Variable signatureTechnician unterscheidet sich nur in der CoreSQl WHERE-Klausel:

signatureTechnician.id = ${elements.filter(function (e) { return e.elementId === ‚z_f_sf_podpistech‘ })[0].value}

)

Hinweis: Wenn wir nur die ID des Attachment-Objekts benötigen, um die binären Unterschriftsdaten abzurufen, und wir keine anderen Informationen aus dem Attachment-Objekt-Datensatz weiterleiten müssen, können wir diese beiden Variablen weglassen.

Wir verwenden zwei FSM Webhook-Aktionen, die die Daten-API-Endpunkte aufrufen, um die Signaturdaten aus der Datenbank in unserer Regel abzurufen:

  1. Aktion, die die Unterschrift des Kunden in der Variablen „signatureCustomerContent“ abruft. Überwindung der Beschränkungen von Formularen mit Google-Formularen-4
  2. Die Aktion, mit der die Unterschrift des Technikers in die Variable „signatureTechnicianContent“ gezogen wird, sieht für die Variable „signatureCustomerContent“ gleich aus und unterscheidet sich nur durch die ID, die zum Abrufen der Unterschrift verwendet wird.


Kommentar:
In dieser Aktion verwenden wir die Variable $ {signatureCustomer.id} – ID des Unterschriftendatensatzes in der Tabelle Attachment, um die Unterschrift des Kunden aus dem aktuell bearbeiteten Vorgang zu extrahieren.

Zusätzlich zu dieser Variable verwenden wir die Systemvariablen $ {account.name} und $ {company.name} sowie unsere eigenen Variablen ${clientID} und ${clientSecret}, über die Sie hier mehr lesen können, um die Portabilität der Regel zu erleichtern.

Die Rückgabewerte von „signatureCustomerContent“ und „signatureTechnicianContent“ enthalten binäre Unterschriftsdaten im Latin1-Zeichensatz, die wir nach einiger zusätzlicher Verarbeitung an Google Forms senden

Die Aktion, die Daten an Google Forms sendet

Action: Webhook

Execution Count: 1

Method: Post

URL: ${google-forms-api-url}

Headers: none

Content Type: application/json

Body:

${

// https://github.com/beatgammit/base64-js

(function(a){if(„object“==typeof exports&&“undefined“!=typeof module)module.exports=a();else if(„function“==typeof define&&define.amd)define([],a);else{var b;b=“undefined“==typeof window?“undefined“==typeof global? „undefined“==typeof self?this:self:global:window,b.base64js=a()>(function(){return function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f=“function“==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error(„Kann Modul ‚“+j+“‚“) nicht finden;throw c.code=“MODULE_NOT_FOUND“,c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h=“function“==typeof require&&require,c=0;c<g.length;c++)a(g[c]);return a}return b}()({„/“:[function(a,b,c){‚use strict=““ d=““ b=“a.length;if(0<b new error string. length must be a multiple of c=’a.indexOf(“ e=““ f=““ m=““]h?g-4:g;for(c=0;c<n;c+=4)b=l[a .charCodeAt(c)]<<18|l[a .charCodeAt(c+1)]<<12|l[a .charCodeAt(c+2)]<<6|l[a .charCodeAt(c+3)],j[k++]=255&b>>16,j[k++]=255&b>>8,j[k++]=255&b;return 2===h&&(b=l[a .charCodeAt(c)]<<2|l[a .charCodeAt(c+1)]>>4,j[k++]=255&b),1===h&&(b=l[a .charCodeAt(c)]<<10|l[a .charCodeAt(c+1)]<<4|l[a .charCodeAt(c+2)]>>2,j[k++]=255&b>>8,j[k++]=255&b),j}Funktion g(a){Rückgabe k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}function h(a,b,c){for(var d,e=[],f=b;f<c;f+=3)d=(16711680&a[f]<<16)+(65280&a[f+1]<<8)+(255&a[f+2]),e.push(g(d));return e.join(„“)}function j(a){for(var b,c=a.length,d=c%3,e=[],f=16383,g=0,j=c-d;g<j;g+=f)e.push(h(a,g,g+f>j?j:g+f));return 1===d?(b=a[c-1],e.push(k[b>>2]+k[63&b<<4]+“==“)):2===d&&(b=(a[c-2]<<8)+a[c-1],e.push(k[b>>10]+k[63&b>>4]+k[63&b<<2]+“=“)),e.join(„“)}c.byteLength=function(a){var b=d(a),c=b[0],e=b[1];return 3*(c+e)/4-e},c.toByteArray=f,c.fromByteArray=j;for(var k=[],l=[],m=“undefined“==typeof Uint8Array?Array:Uint8Array,n=“ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/“,o=0,p=n.length;o<p;++o)k[o]=n[o],l[n .charCodeAt(o)]=o;l[45]=62,l[95]=63},{}]},{},[])(„/“)});

function latin1ToUint8Array(str) {
return Array.prototype.map.call(str, function (c) { return c.charCodeAt(0) });
}

function findWithFallback(arr, fn) {
var item = arr.filter(fn);
return item.length ? item[0] : {};
}

function MissingValue() { return this.constructor === MissingValue ? this : new MissingValue() }
MissingValue.ensure = function(value) { return value || MissingValue() }
MissingValue.prototype.toString = function() { return ‚[object MissingValue]‘ }
MissingValue.prototype.toJSON = function() { return null }

// Fallbacks – wir wollen den Fehler „kann Eigenschaft … von undefiniert nicht lesen“ vermeiden
serviceCall = MissingValue.ensure(serviceCall);
Vertrag = MissingValue.ensure(Vertrag);
businessPartner = MissingValue.ensure(businessPartner);
Adresse = MissingValue.ensure(Adresse);
Techniker = MissingValue.ensure(Techniker);
signatureTechnician = MissingValue.ensure(signatureTechnician);
signatureCustomer = MissingValue.ensure(signatureCustomer);
elements = elements || [];
creds = creds || [];
Geräte = Geräte || [];

JSON.stringify({
„Ereignis“: „pdf“,
„podpis_obrazok_1“: signatureTechnician instanceof MissingValue
? null
base64js.fromByteArray(latin1ToUint8Array(signatureTechnicianContent)),
„podpis_typ_1“: signatureTechnician.type,
„podpis_obrazok_2“: signatureCustomer instanceof MissingValue
? null
base64js.fromByteArray(latin1ToUint8Array(signatureCustomerContent)),
„podpis_typ_2“: SignaturKunden.typ,
})
}

(Abgekürztes Beispiel unter Auslassung der Zuordnung der übrigen Variablen).

Diesmag auf den ersten Blick kompliziert erscheinen, aber wir glauben, dass Sie Ihre Meinung darüber ändern werden, wenn wir sie in den folgenden Zeilen durchgehen. Der einschüchterndste Codeblock befindet sich direkt am Anfang der Aktion:

https://github.com/beatgammit/base64-js

(function(a){if(„object“==typeof exports&&“undefined“!=typeof module)module.exports=a();else if(„function“==typeof define&&define.amd)define([],a);else{var b;b=“undefined“==typeof window?“undefined“==typeof global? „undefined“==typeof self?this:self:global:window,b.base64js=a()>(function(){return function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f=“function“==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error(„Kann Modul ‚“+j+“‚“) nicht finden;throw c.code=“MODULE_NOT_FOUND“,c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h=“function“==typeof require&&require,c=0;c<g.length;c++)a(g[c]);return a}return b}()({„/“:[function(a,b,c){‚use strict=““ d=““ b=“a.length;if(0<b new error string. length must be a multiple of c=’a.indexOf(“ e=““ f=““ m=““]h?g-4:g;for(c=0;c<n;c+=4)b=l[a .charCodeAt(c)]<<18|l[a .charCodeAt(c+1)]<<12|l[a .charCodeAt(c+2)]<<6|l[a .charCodeAt(c+3)],j[k++]=255&b>>16,j[k++]=255&b>>8,j[k++]=255&b;return 2===h&&(b=l[a .charCodeAt(c)]<<2|l[a .charCodeAt(c+1)]>>4,j[k++]=255&b),1===h&&(b=l[a .charCodeAt(c)]<<10|l[a .charCodeAt(c+1)]<<4|l[a .charCodeAt(c+2)]>>2,j[k++]=255&b>>8,j[k++]=255&b),j}Funktion g(a){Rückgabe k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}function h(a,b,c){for(var d,e=[],f=b;f<c;f+=3)d=(16711680&a[f]<<16)+(65280&a[f+1]<<8)+(255&a[f+2]),e.push(g(d));return e.join(„“)}function j(a){for(var b,c=a.length,d=c%3,e=[],f=16383,g=0,j=c-d;g<j;g+=f)e.push(h(a,g,g+f>j?j:g+f));return 1===d?(b=a[c-1],e.push(k[b>>2]+k[63&b<<4]+“==“)):2===d&&(b=(a[c-2]<<8)+a[c-1],e.push(k[b>>10]+k[63&b>>4]+k[63&b<<2]+“=“)),e.join(„“)}c.byteLength=function(a){var b=d(a),c=b[0],e=b[1];return 3*(c+e)/4-e},c.toByteArray=f,c.fromByteArray=j;for(var k=[],l=[],m=“undefined“==typeof Uint8Array?Array:Uint8Array,n=“ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/“,o=0,p=n.length;o<p;++o)k[o]=n[o],l[n .charCodeAt(o)]=o;l[45]=62,l[95]=63},{}]},{},[])(„/“)});

Dieser Abschnitt definiert eine Javascript-Funktion zur Kodierung der Binärdaten von Unterschriftenbildern in das base64-Format, in dem wir Unterschriften an Google Forms senden. Wir führen diese Umwandlung durch, um die Integrität dieser Daten bei der Übermittlung zu wahren. Da das SAP FSM-System den Import oder die Installation von Javascript-Modulen innerhalb der Regeln nicht unterstützt, wie wir es von der Standard-Anwendungsentwicklung gewohnt sind, war es in unserem Fall notwendig, die entsprechenden Funktionen im Body der Aktion zu definieren. Um die Quelle dieser Definition zu erfassen, enthält unser Textkörper im kommentierten Abschnitt einen Link zu dem GitHub-Repository, aus dem sie stammt – https://github.com/beatgammit/base64-js.

Die Funktion latin1ToUint8Array liefert dann den richtigen Zeichensatz für die Verwendung der base64-Kodierung durch die Funktion fromByteArray

function latin1ToUint8Array(str) {
return Array.prototype.map.call(str, function (c) { return c.charCodeAt(0) });
}

Damit die JSON.stringify()-Methode erfolgreich ausgeführt werden kann, müssen Backup-Werte definiert werden, für den Fall, dass die verwendeten Variablen leer sind. Andernfalls könnte diese Methode einen Fehler verursachen – ‚cannot read property … of undefined‘, was zu einem Fehler beim Senden von Daten an Google Forms führen würde. Die Verfügbarkeit von Sicherungswerten wird durch die folgenden Codeblöcke gewährleistet:

function findWithFallback(arr, fn) {
var item = arr.filter(fn);
return item.length ? item[0] : {};
}

function MissingValue() { return this.constructor === MissingValue ? this : new MissingValue() }
MissingValue.ensure = function(value) { return value || MissingValue() }
MissingValue.prototype.toString = function() { return ‚[object MissingValue]‘ }
MissingValue.prototype.toJSON = function() { return null }

// Fallbacks – wir wollen den Fehler „kann Eigenschaft … von undefiniert nicht lesen“ vermeiden
serviceCall = MissingValue.ensure(serviceCall);
Vertrag = MissingValue.ensure(Vertrag);
businessPartner = MissingValue.ensure(businessPartner);
Adresse = MissingValue.ensure(Adresse);
Techniker = MissingValue.ensure(Techniker);
signatureTechnician = MissingValue.ensure(signatureTechnician);
signatureCustomer = MissingValue.ensure(signatureCustomer);
elements = elements || [];
creds = creds || [];
Geräte = Geräte || [];

Am Ende unseres action bodys müssen nur noch die einzelnen Variablen auf die Attribute der auf der Google Forms-Seite erstellten API gemappt werden, zusammen mit der Umwandlung von Javascript-Objekten in JSON-Strings mithilfe der Methode JSON.stringify ():

JSON.stringify({
„Ereignis“: „pdf“,
„podpis_obrazok_1“: signatureTechnician instanceof MissingValue
? null
base64js.fromByteArray(latin1ToUint8Array(signatureTechnicianContent)),
„podpis_typ_1“: signatureTechnician.type,
„podpis_obrazok_2“: signatureCustomer instanceof MissingValue
? null
base64js.fromByteArray(latin1ToUint8Array(signatureCustomerContent)),
„podpis_typ_2“: SignaturKunden.typ,
})

(Abgekürztes Beispiel unter Auslassung der Zuordnung der übrigen Variablen)

Wie Sie sehen können, verwenden wir in diesem letzten Schritt die oben definierten Funktionen„fromByteArray“ und„latin1ToUint8Array“ für die korrekte Kodierung der Unterschriftendaten, so dass sie aus diesen Informationen in ihre grafische Form in Google Forms rekonstruiert werden können, um sie im endgültig generierten .pdf-Protokoll zu verwenden.

Tomáš Potzy, CX-Berater