6. dubna 2022

Jak obejít omezení formulářů SAP FSM pomocí Google Forms

Inteligentní formuláře SAP FSM stále nejsou tak chytré, jak bychom si přáli. V případech, kdy je nabídka možností odpovědí závislá na hodnotě předchozího pole formuláře, má nástroj SAP FSM omezení. Naštěstí lze použít jednoduché a bezplatné Google forms.

FSM sice nabízí možnost vytvářet Smartformuláře přímo prostřednictvím nativního editoru, nicméně tento způsob má jednu velkou limitaci a tou je absence dynamického generování těchto protokolů na základě vyplněných údajů. (Mimo skrytí a zobrazení některých sekcí, přičemž dokáže reagovat pouze na data obsažená přímo ve Smartformuláři, ne např. na Materiál vytvořený na Aktivitě).

V případě jednoho z našich zákazníků z oblasti telekomunikací jsme se tak během tohoto projektu rozhlédli, po jiných flexibilnějších možnostech generování těchto protokolů. Jako nejideálnější řešení jsme zvolili integraci na Google Forms, která zpracovává a dynamicky doplňuje potřebné sekce, určuje design daného protokolu a ve výsledku generuje jeho PDF verzi spolu s jejím odesláním na zákazníka.

Pro odeslání potřebných informací do tohoto nástroje jsme proto vytvořili v SAP FSM byznys pravidlo, shromažďující všechny potřebné informace zajišťující také jejich příslušný formát očekávaný nástrojem Google Forms.

 

Definice byznys pravidla:

Nejdůležitějším nastavením v definici tohoto pravidla, je v tomto případě výběr možnosti použití plné podpory Javascriptových výrazů. Hned ze dvou důvodů:

  1. Výběr možnosti bez podpory těchto výrazů je zastaralý (deprecated)
  2. V dalších sekcích tohoto pravidla budeme potřebovat aktivně využívat Javascriptové funkce, pro správné naformátování dat odesílaných do Google Forms
overcoming-fsm-form-limitations-with-google-forms-1

Trigger (Spouštěč):

overcoming-fsm-form-limitations-with-google-forms-2

Je možné podle potřeb použít jakýkoli. V našem případě jsme z několika důvodů, ať už procesních, tak optimalizace výkonu, zvolili následující:

Variables (proměnné)

overcoming-fsm-form-limitations-with-google-forms-3

V tomto blogu se nebudeme věnovat všem, ale soustředíme se pouze na ty, které zajišťují odeslání podpisů  technika a zákazníka.


Definice a komentář jednotlivých proměnných

Název proměnné: elements

Typ proměnné: Array

Definice 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_podpisucas‘, ‚z_f_sf_podpistech‘, ‚z_f_sf_typvyjazdu‘, ‚z_f_sf_techpoznamka‘) AND ci.object.objectId = ${activity.id}

DTO: ChecklistInstance.18;ChecklistInstanceElement.17

Komentář:  V rámci zadání od našeho zákazníka bylo nezbytné, aby výsledný PDF protokol obsahoval podpisy obou stran (technika a zákazníka). Pro jejich odeslání do Google Forms, jsme tak potřebovali z datatabáze systému FSM načíst jejich data v binární podobě enkódovaná, jako base64 řetězec. Pro tento účel nejprve vytahujeme ze smartformuláře (checklistu) ID záznamů těchto obrázků uložených v objektu Attachment, na základě názvů těchto elementů (‚z_f_sf_podpistech‘ a ‚z_f_sf_podpisucas‘), které jsme zadefinovali v rámci SmartServu. Pomocí těchto ID

Jako část requirement z našeho customer, to bylo necessary, že v důsledku dokumentu protokolu protokolu byly uvedeny signatures of both parties (technické and customer). To send them do Google formuláře, we needed to retrieve their data from the FSM database v binární formě-encoded jako base64 string. Pro tento purpose, jsme první extrakt z inteligentního formuláře (checklist) záznamu ID těchto obrazů zastoupených v objektu objektu, založeného na názvech těchto prvků (‚z_f_sf_podpistech‘ and ‚z_f_sf_podpisucas‘), které jsou definované out by the technician executing the Service call. Pomocí těchto ID, jsou získány jejich záznamy z objektu objektu pomocí „signatureCustomer“ and „signatureTechnician“ variables:

Název proměnné (Variable Name): signatureCustomer

Typ proměnné (Variable Type): Object

Typ objektu (Object Type): Attachment

Definice CoreSQL (CoreSQL WHERE Clause):

  • id = ${elements.filter(function (e) { return e.elementId === ‚z_f_sf_podpisucas‘ })[0].value}
  • (proměnná signatureTechnician se liší pouze v CoreSQl WHERE Clause:

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


Poznámka:
V případě, že nám stačí pouze ID objektu Attachment pro dotažení binárních dat podpisů a nepotřebujeme zasílat dál, žádnou jinou informaci nacházející se v záznamu objektu Attachment, můžeme tyto dvě proměnné vynechat.

Pro načtení dat jednotlivých podpisů v pravidle, používáme dvě akce typu FSM Webhook volající /data API systému FSM:

  1. Akce dotahující podpis zákazníka do proměnné „signatureCustomerContent“overcoming-fsm-form-limitations-with-google-forms-4
  2. Akce dotahující podpis technika do proměnné „signatureTechnicianContent“, vypadá stejně, jako signatureCustomerContent, liší se pouze v použitém ID pro načítání podpisu.

Komentář:  V této akci využíváme proměnnou ${signatureCustomer.id} – ID záznamu podpisu v tabulce Attachment (Přílohy), pro vytažení podpisu zákazníka z aktuálně zpracovávané aktivity.

Kromě této proměnné pak pro snadnější přenositelnost pravidla mezi systémy využíváme systémové proměnné – ${account.name} a ${company.name} + naše vlastní ${clientID} a ${clientSecret} o kterých se můžete více dočíst tu.

Vrácené hodnoty „signatureCustomerContent“ a „signatureTechnicianContent“ obsahují binární data podpisů ve znakové sadě Latin1, které následnou akcí odešleme do Google Forms. V dané akci data ještě upravujeme do formátu dle požadavků Google Forms.

Akce pro odeslání dat do Google Forms

Action: Webhook

Execution Count: 1

Method: Post

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

Headers: žádné

Content-Type: application/json

Body:

${

//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‘;function d(a){var b=a.length;if(0<b%4)throw new Error(„Invalid string. Length must be a multiple of 4“);var c=a.indexOf(„=“);-1===c&&(c=b);var d=c===b?0:4-c%4;return[c,d]}function e(a,b,c){return 3*(b+c)/4-c}function f(a){var b,c,f=d(a),g=f[0],h=f[1],j=new m(e(a,g,h)),k=0,n=0<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}function g(a){return 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 – we want to avoid ‚cannot read property … of undefined‘ error
serviceCall = MissingValue.ensure(serviceCall);
contract = MissingValue.ensure(contract);
businessPartner = MissingValue.ensure(businessPartner);
address = MissingValue.ensure(address);
technician = MissingValue.ensure(technician);
signatureTechnician = MissingValue.ensure(signatureTechnician);
signatureCustomer = MissingValue.ensure(signatureCustomer);
elements = elements || [];
creds = creds || [];
devices = devices || [];

JSON.stringify({
  „event“: „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,
})
}

(Zkrácený příklad s vynecháním mapování zbylých proměnných)

Tato akce může na první pohled vypadat hrozivě, avšak po jejím vysvětlení v následujících řádcích na ni podle nás rychle změníte názor.

Jako první do očí hned udeří blok kódu v úvodu těla této akce: 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‘;function d(a){var b=a.length;if(0<b%4)throw new Error(„Invalid string. Length must be a multiple of 4“);var c=a.indexOf(„=“);-1===c&&(c=b);var d=c===b?0:4-c%4;return[c,d]}function e(a,b,c){return 3*(b+c)/4-c}function f(a){var b,c,f=d(a),g=f[0],h=f[1],j=new m(e(a,g,h)),k=0,n=0<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}function g(a){return 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},{}]},{},[])(„/“)});

Tato část definuje javascriptovou funkci, pro enkódování binárních dat obrázků podpisů do base64 formátu, ve kterém posíláme podpisy do Google Forms. Tuto konverzi provádíme, aby během odesílání těchto dat byla zachována jejich integrita. Jelikož systém SAP FSM, v rámci pravidel nepodporuje import, zda instalaci javascriptových modulů, tak jak jsme na to zvyklí při standardním vývoji aplikací, bylo v našem případě třeba zadefinovat relevantní funkce, takto na začátku body akce. Pro zachycení zdroje této definice obsahuje naše body v zakomentované sekci odkaz na GitHub repository ze které pochází – https://github.com/beatgammit/base64-js.

Funkce latin1ToUint8Array pak zajišťuje správnou znakovou sadu pro použití base64 enkódování přes funkci fromByteArray:

function latin1ToUint8Array(str) {

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

Pro úspěšné sběhnutí metody JSON.stringify() je třeba zadefinovat záložní hodnoty, pro případ, že by byly použity proměnné prázdné, jinak by při této metodě mohlo dojít k chybě – ‚cannot read property … of undefined‘, jejímž následkem by bylo odeslání dat do Google Forms neúspěšné. Záložní hodnoty zajišťují následující odstavce:

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 – we want to avoid the ‚cannot read property … of undefined‘ error
serviceCall = MissingValue.ensure(serviceCall);
contract = MissingValue.ensure(contract);
businessPartner = MissingValue.ensure(businessPartner);
address = MissingValue.ensure(address);
technician = MissingValue.ensure(technician);
signatureTechnician = MissingValue.ensure(signatureTechnician);
signatureCustomer = MissingValue.ensure(signatureCustomer);
elements = elements || [];
creds = creds || [];
devices = devices || [];

V závere body našej akcie potom už iba ostáva namapovať jednotlivé premenné na atribúty API vytvorenej na strane Google Forms, spolu s konverziou Javascriptových objektov na JSON string pomocou metódy JSON.stringify():

JSON.stringify({
  „event“: „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,
})

Zkrácený příklad s vynecháním mapování zbylých proměnných)

Jak můžete vidět, tak právě v tomto posledním kroku využíváme výše zadefinované funkce „fromByteArray“ a „latin1ToUint8Array“, pro správné enkódování dat podpisů, tak aby mohly být z těchto informace na druhé straně opět zrekonstruovány do jejich grafické podoby, kterou lze použít ve finálním vygenerovaném .pdf protokolu.


Tomáš Potzy
, CX Consultant