6. april 2022

Overvindelse af begrænsninger i FSM-formularer med Google Forms

FSM-smarte formularer er stadig ikke så smarte, som mange af os gerne ville ønske. I tilfælde, hvor pulldown-menuen er afhængig af værdien af et tidligere formularfelt, er dette værktøj simpelthen ikke smart nok. Heldigvis kan du bruge enkle og gratis google-formularer.

Selv om FSM giver mulighed for at oprette Smartforms direkte via den native editor, har denne metode en stor begrænsning, nemlig at der ikke er mulighed for dynamisk generering af disse protokoller baseret på de indtastede data. (Bortset fra at skjule og vise nogle sektioner. Den er kun i stand til at reagere på data, der er indeholdt direkte i Smartform, ikke f.eks. på materiale, der er oprettet i aktiviteten.)

For en af vores kunder inden for telekommunikation har vi i forbindelse med dette projekt undersøgt andre mere fleksible muligheder for at generere disse protokoller. Som den mest ideelle løsning valgte vi integration på Google Forms, som behandler og dynamisk udfylder de nødvendige sektioner, bestemmer udformningen af protokollen og genererer en PDF-version af den sammen med dens afsendelse til kunden.

Til denne integration brugte vi forretningsregelfunktionaliteten på SAP FSM-siden til at oprette en forretningsregel for at sende og formatere de oplysninger, der er nødvendige for at generere den endelige protokol i Google-formularer, som sendes til kunden.

Definition af forretningsregler

Den vigtigste indstilling i definitionen af denne regel er valget af at bruge fuld understøttelse af Javascript-udtryk. Nemlig af disse to grunde:

  1. Valget af indstilling uden støtte for disse udtryk er forældet.
  2. I de næste afsnit af denne regel skal vi aktivt bruge Javascript-funktioner til korrekt formatering af data, der sendes til Google Forms.
overvinde-fsm-form-begrænsninger-med-google-forms-1

Udløser

at overvinde begrænsningerne i forbindelse med formularer med Google-formularer-2

Afhængigt af dine specifikke krav kan du vælge en af dem. I vores tilfælde har vi valgt den følgende af flere grunde (f.eks. optimering af ydeevne og datatilgængelighed):

Variabler

at overvinde begrænsningerne i forbindelse med formularer med Google-formularer-3

I denne blog vil vi ikke komme ind på hver enkelt af dem, men vi vil kun fokusere på dem, der arbejder med teknikeres og kunders underskrifter.

Definitioner og kommentarer til de enkelte variabler

Variabelt navn: elementer
Variabel type: Array
Definition af CoreSQL:

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

JOIN ChecklistInstanceElement cie ON ci.id = cie.checklistInstance

WHERE cie.elementId IN (‘z_f_sf_sf_podpisucas’, ‘z_f_sf_sf_podpistech’, ‘z_f_sf_sf_typvyjazdu’, ‘z_f_sf_sf_techpoznamka’) AND ci.object.object.objectId = ${activity.id}

DTO: ChecklistInstance.18;ChecklistInstanceElement.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_sf_podpistech” a “z_f_sf_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”:

Som en del af kravet fra vores kunde var det nødvendigt, at den resulterende PDF-protokol indeholdt begge parters (tekniker og kunde) underskrifter. For at sende dem til Google Forms skulle vi hente deres data fra FSM-databasen i binær form – kodet som en base64-streng. Til dette formål uddrager vi først fra den smarte formular (tjekliste) post ID’erne for disse billeder, der er gemt i Attachment-objektet, baseret på navnene på disse elementer (“z_f_sf_sf_podpistech” og “z_f_sf_sf_podpisucas”), som vi definerede i den smarte formular, der blev udfyldt af den tekniker, der udførte serviceopkaldet. Ved hjælp af disse ID’er henter vi derefter deres poster fra Attachment-objektet ved hjælp af variablerne“signatureCustomer” og “signatureTechnician”:

Variabel Navn: signatureCustomer
Variabel type: Objekt
Objekttype: Vedhæftning
CoreSQL WHERE-klausul:

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

– (Variablen signatureTechnician er kun forskellig i CoreSQl WHERE-klausulen:

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

)

Bemærk: Hvis vi kun har brug for ID’et for Attachment-objektet til at hente de binære signaturdata, og vi ikke har brug for at videresende andre oplysninger, der findes i Attachment-objektet, kan vi udelade disse to variabler.

Vi bruger to FSM Webhook-aktioner, der kalder Data API-endpunkterne for at hente signaturdataene fra databasen i vores regel:

  1. Handling, der henter kundens underskrift i variablen “signatureCustomerContent”. at overvinde begrænsningerne i forbindelse med formularer med Google-formularer-4
  2. Handlingen, der trækker teknikerenes signatur ind i variablen “signatureTechnicianContent”, ser ud som den samme for signatureCustomerContent, idet den kun adskiller sig ved det ID, der bruges til at hente signaturen.


Kommentar:
I denne handling bruger vi variablen $ {signatureCustomer.id} – ID for signaturposten i tabellen Attachment – til at udtrække kundens signatur fra den aktuelle aktivitet.

Ud over denne variabel bruger vi systemvariablerne – $ {account.name} og $ {company.name} + vores egne ${clientID} og ${clientSecret}, som du kan læse mere om her for at gøre det lettere at overføre reglen.

Returværdierne “signatureCustomerContent” og “signatureTechnicianContent” indeholder binære signaturdata i Latin1-tegnsæt, som vi efter yderligere behandling sender til Google Forms

Handlingen, der sender data til Google Forms

Handling: Webhook
Antal udførelser: 1
Metode: Post
URL: ${google-forms-api-url}
Overskrifter: ingen
Content-Type: application/json
Kroppen:

${

// 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(“Cannot find module ‘”+j+”‘”);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){return k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}funktion 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=”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopnopqrstuvwxyz0123456789+/”,o=0,p=n.længde;o<p;++o)k[o]=n[o],l[n .charCodeAt(o)]=o;l[45]=62,l[95]=63},{}]},{},[])(“/”)});

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

funktion 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 – vi ønsker at undgå fejlen “cannot read property … of undefined” (kan ikke læse egenskab … af udefineret)
serviceCall = MissingValue.ensure(serviceCall);
kontrakt = MissingValue.ensure(kontrakt);
businessPartner = MissingValue.ensure(businessPartner);
address = MissingValue.ensure(address);
technician = MissingValue.ensure(technician);
signatureTechnician = MissingValue.ensure(signatureTechnician);
signatureCustomer = MissingValue.ensure(signatureCustomer);
elementer = elementer || [];
creds = creds || [];
enheder = enheder || [];

JSON.stringify({
“begivenhed”: “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”: signatureCustomer.type,
})
}

(Forkortet eksempel, hvor man udelader kortlægningen af de resterende variabler).

Denne begivenhed kan virke kompliceret ved første øjekast, men vi tror, at du vil ændre din opfattelse af den, når vi gennemgår den i de følgende linjer.

Den mest skræmmende kodeblok er placeret lige i begyndelsen af handlingen:

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(“Cannot find module ‘”+j+”‘”);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){return k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}funktion 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=”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopnopqrstuvwxyz0123456789+/”,o=0,p=n.længde;o<p;++o)k[o]=n[o],l[n .charCodeAt(o)]=o;l[45]=62,l[95]=63},{}]},{},[])(“/”)});

I dette afsnit defineres en javascript-funktion til kodning af signaturbilledernes binære data til base64-formatet, som vi sender signaturer til Google Forms i. Vi foretager denne konvertering for at bevare integriteten af disse data, mens de sendes. Da SAP FSM-systemet ikke understøtter import eller installation af javascript-moduler i reglerne, som vi er vant til i standard applikationsudvikling, var det i vores tilfælde nødvendigt at definere de relevante funktioner i handlingens krop. For at indfange kilden til denne definition indeholder vores krop i det kommenterede afsnit et link til GitHub-arkivet, hvorfra den stammer – https://github.com/beatgammit/base64-js.

Funktionen latin1ToUint8Array giver derefter det korrekte tegnsæt til brug af base64-kodning via funktionen fromByteArray

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

For at JSON.stringify()-metoden kan køre med succes, er det nødvendigt at definere backup-værdier, hvis de anvendte variabler er tomme, ellers kan denne metode forårsage en fejl – “cannot read property … of undefined”, hvilket vil resultere i en fejl under afsendelse af data til Google Forms. Tilgængeligheden af backup-værdier sikres af følgende kodeblokke:

funktion 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 – vi ønsker at undgå fejlen “cannot read property … of undefined” (kan ikke læse egenskab … af udefineret)
serviceCall = MissingValue.ensure(serviceCall);
kontrakt = MissingValue.ensure(kontrakt);
businessPartner = MissingValue.ensure(businessPartner);
address = MissingValue.ensure(address);
technician = MissingValue.ensure(technician);
signatureTechnician = MissingValue.ensure(signatureTechnician);
signatureCustomer = MissingValue.ensure(signatureCustomer);
elementer = elementer || [];
creds = creds || [];
enheder = enheder || [];

I slutningen af vores action body er det eneste, der er tilbage, at mappe de enkelte variabler til attributterne for API’et, der er oprettet på Google Forms-siden, sammen med konverteringen af Javascript-objekter til JSON-strenge ved hjælp af metoden JSON.stringify ():

JSON.stringify({
“begivenhed”: “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”: signatureCustomer.type,
})

(Forkortet eksempel, hvor man udelader kortlægningen af de resterende variabler)

Som du kan se, bruger vi i dette sidste trin de ovenfor definerede funktioner“fromByteArray” og“latin1ToUint8Array” til korrekt kodning af signaturdata, så de kan rekonstrueres fra disse oplysninger til deres grafiske form i Google Forms til brug i den endelige genererede .pdf-protokol.

Tomáš Potzy, CX-konsulent