Supplier technical control (KES)
KES available only on the Retail Ops KES (Kitchen Expert Suite) version
Functioning
The supplier control tools allow you to control your kitchen before sending it via EDI. If you wish to use another tool than those provided by Retail Ops (TAK AND ATAK), or if these do not suit you, then you can find here a way to integrate the tool of your choosing yourself.
The integration of your tool is done using an iFrame Synchronous notifications. The iframe will need to connect to our system using your external API user. Please note the user must have an external profile. Set site in the iFrame will have to handle the communicate between the external tool and Retail operations. We need to have this URL in our system in order to be able to call you. Here and example of url : https://YOUR_URL/?documentTypeID={documentTypeID}&documentID={documentID}&supplierID={supplierID}&localisation={localisation}&winLangID={winLangID}&supplierCode={supplierCode}&storeEDIReference={storeEDIReference}
The url will be called with several parameters :
- documentTypeID --> Type of document (Quotation, order, ...)
- documentID --> Identifier of the document
- supplierID --> Identifier of the supplier
- supplierCode --> The supplier code as filled in the Supplier edi parameters
- storeEDIReference --> the edi referent of the store for the supplier
- localisation --> Retail operations start-up language (fr/en/sp, ...)
- winLangID --> Windows Language Identifier
Supported postMessages
We have postMessages so you can customize your window:
MessageType screenSize: to customize the size (height/width) of the window, using the properties (you will then need to manage the responsiveness of your window):
- 'Width': string in percentage or pixels, for example: "90%" or "750px"
- 'Height': string in percentage or pixels, for example: "90%" or "750px"
Please note, if the size exceeds 100% of your screen in width or height, it will exceed the screen and you will be stuck on this window. Same for under or equal to 0%.
{
"MessageType": "screenSize",
"Width": "90%",
"Height": "90%"
}
MessageType setTitle: translate the window title, with the "Title" property being a string, for example: "Window Title"
{
"MessageType": "setTitle",
"Title": "Window Title"
}
When customizing labels, you will need to manage the translation by yourself.
If postMessages are not used, RetailOps will use its default values.
Please note that using these postMessages could have a visual impact when the page is opened.
Configuration
If you use you own tool, once you have created your iFrame you must contact your commercial contact with the url for it to be integrated in our solution, or you you use TAK or ATAK also contact you commercial with you tool credentials to enable the feature.
Then you will have to setup your suppliers (EDI Setup) with all the correct edi informations and select the appropriate tool.
Basic use case
When the iframe is opened:
- The iframe backend call our webservice to generate the EDI for the document specified in the iframe url.
- The backend call the control tool with the generated edi
- and finally display the result of the control.
Example With ATAK (#example)
Description of the example
The example is in PHP and HTML.
If you want to use our example you need to change multiple keys in the script.
Below are the details of the values corresponding to these parameters:
PHP :
{ATAK_URL} --> Provider URL (SANDBOX or PROD)
{USERNAME_ATAK} --> Provider Username
{PSWD_ATAK} --> Provider password
{URL_SMO_WEBSITE} --> https://SITE.storemanager.online/SITE/
{SMO_KEY} --> Access key provided by Dassault Systèmes
{LOGIN} --> Authentication login (Profile: External) provided by Dassault Systèmes
{PASSWORD} --> Authentication password (Profile: External) provided by Dassault Systèmes
{AUTH_SMO} --> Client ID provided by Dassault Systèmes \
HTML
<!DOCTYPE HTML>
<html lang="" style="height:100%;">
<head>
<title>Atak</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<link rel="stylesheet" href="Tak.css">
<script>
function GetUrlParameter(param){
var params = window.location.search.substring(1).split('&');
for(var i = 0; i < params.length; i++){
var paramName = params[i].split('=');
if(paramName[0] == param){
return paramName[1];
}
}
}
OpenWindow = function(){
var params = new URLSearchParams();
axios.post('Atak.php?action=getHtml&documentTypeID='+documentTypeID+ '&documentID='+documentID+'&supplierID=' + supplierID+'&storeEDIReference=' + storeEDIReference+'&supplierCode=' + supplierCode + '&localisation=' + localisation, params).then(function(resp) {
if(resp.status == 200 && (resp.statusText == "OK" || resp.statusText == "")){
document.getElementById("beforeSend").style.display = 'none';
const blob = new Blob([resp.data], { type: "text/html" });
const url = URL.createObjectURL(blob);
document.getElementById("backFromAtak").src = url;
} else {
document.getElementById("msgSend").innerHTML = "ATAK NOT RESPOND";
document.getElementById("backFromAtak").disabled = true;
}
});
}
var documentTypeID = GetUrlParameter('documentTypeID');
var documentID = GetUrlParameter('documentID');
var supplierID = GetUrlParameter('supplierID');
var localisation = GetUrlParameter('localisation');
var winLangID = GetUrlParameter('winLangID');
var supplierCode = GetUrlParameter('supplierCode');
var storeEDIReference = GetUrlParameter('storeEDIReference');
switch (localisation) {
case 'fr': localisation = 'fr-FR'; break;
case 'ch':
case 'cn':
case 'zh': localisation = 'zh-CN'; break;
case 'sp':
case 'es': localisation = 'es-ES'; break;
case 'de':
case 'ge': localisation = 'de-DE'; break;
case 'nl': localisation = 'nl-NL'; break;
case 'gb': localisation = 'en-GB'; break;
case 'en': localisation = 'en-US'; break;
};
parent.postMessage({MessageType:'screenSize', Width: "70%", Height: "70%"}, '*');
</script>
</head>
<body onload="OpenWindow()" style="height:96%;">
<div id="beforeSend">
<p class="center">Demande faite à ATAK pour la validation de la cuisine</p>
<div class="loader" id="loader-1"></div>
</div>
<iframe
id="backFromAtak"
:src="srcdoc"
sandbox="allow-scripts allow-same-origin"
frameBorder="0"
width="100%"
height="100%">
</iframe>
</body>
</html>
PHP
<?php
function authSMO($authToken, $authClientId){
$curl = curl_init();
$urlRESTGetInfos = sprintf("%sRESTWS/api/auth/v1/connectAutoWithAuthToken/?authToken=%s&authClientID=%s", $GLOBALS['url'], $authToken, $authClientId);
curl_setopt_array($curl, [
CURLOPT_URL=> $urlRESTGetInfos,
CURLOPT_RETURNTRANSFER=> true,
CURLOPT_SSL_VERIFYPEER=> false
]);
$response = curl_exec($curl);
if ($response == false) {
error_log("CURl Error: " . curl_error($curl));
return false;
}
$content = json_decode($response, true);
$GLOBALS['sessid'] = $content['data']['token'];
curl_close($curl);
return true;
}
function createSpiAuthToken($login, $password, $key) {
// recupration de la date actuelle au format UTC
$UTC = new DateTimeZone("UTC");
$currDate = new DateTime("now", $UTC);
$token = $currDate->format(DATE_ISO8601).'|'.$login.'|'.$password.'|';
if (version_compare(phpversion(), '7.2.0', '<')) {
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$padding = $block - (strlen($token) % $block);
$token .= str_repeat(chr($padding), $padding);
return base64_url_encode(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
base64_decode($key),
$token,
MCRYPT_MODE_CBC,
base64_decode("T0iPmNI7v+XCi7tSy8npYA==")
)
);
} else {
return base64_url_encode(
openssl_encrypt(
$token,
'aes-256-cbc',
base64_decode($key),
$options=OPENSSL_RAW_DATA,
base64_decode("T0iPmNI7v+XCi7tSy8npYA==")
)
);
}
}
// base64_encode URL friendly
function base64_url_encode($input) {
return strtr(base64_encode($input), '+/=', '-_,');
}
// On récupére le ZIP de RO
function getZip($entityTypeID, $entityID, $supplierID){
$curl = curl_init();
$urlGetZip = sprintf("%sRESTWS/api/externals/v1/documents/%s/%s/edi/%s/?winLangID=%s&withAttachment=false&withAllCompatibleCheckSystemSupplier=true"
, $GLOBALS['url']
, $entityTypeID
, $entityID
, $supplierID
, $GLOBALS["winLangID"]
);
curl_setopt_array($curl, [
CURLOPT_URL => $urlGetZip,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => ['X-PHP-SESSID:' . $GLOBALS['sessid'],
'Accept: application/zip',
'Content-Type: application/json'],
CURLOPT_SSL_VERIFYPEER=> false
]);
$response = curl_exec($curl);
$GLOBALS["zip"] = $response;
curl_close($curl);
return true;
}
// Envoie du ZIP a ATAK et retour du HTML (validation de l'EDI)
function sendToAtak(){
$curl = curl_init();
$urlGetZip = sprintf("%s/validateplanning/html"
, $GLOBALS['atakURL']
);
curl_setopt_array($curl, [
CURLOPT_URL => $urlGetZip,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Basic '. $GLOBALS['basicAuth'],
'Accept: text/html',
'Content-Type: application/zip',
'requestedby: HomeByMe',
'language:'. $GLOBALS['localisation'],
'supplier:'. $GLOBALS['supplierCode'], // ExternalCode dans Fournisseur
'buyer:' .$GLOBALS['storeEDIReference'], // storeEDIReference PMStoreSupplierEDI
'customer:'. $GLOBALS['storeEDIReference']
],
CURLOPT_SSL_VERIFYPEER=> false,
CURLOPT_POSTFIELDS => $GLOBALS["zip"]
]);
$response = curl_exec($curl);
$GLOBALS["htmlReturnFromATAK"] = $response;
curl_close($curl);
}
$url = "URL_SMO_WEBSITE";
$key = "SMO_KEY";
$login = "LOGIN";
$password = "PASSWORD";
$authClientId = "AUTH_SMO";
$localisation = "fr-FR";
$token = createSpiAuthToken($login, $password, $key);
$sessid = '';
$winLangID = -1;
$atakURL = "ATAK_URL";
$usernameATAK = "USERNAME_ATAK";
$passwordATAK = "PSWD_ATAK";
$basicAuth = base64_encode($usernameATAK.':'.$passwordATAK);
$htmlReturnFromATAK = "";
if (isset($_GET["action"]))
{
switch ($_GET["action"])
{
case 'getHtml':
// On se connecte a RO
if(authSMO($token, $authClientId)){
$documentTypeID = $_GET["documentTypeID"];
$documentID = $_GET["documentID"];
$supplierID = $_GET["supplierID"];
$localisation = $_GET["localisation"];
$supplierCode = $_GET["supplierCode"];
$storeEDIReference = $_GET["storeEDIReference"];
// On demande le ZIP a RO
if(getZip($documentTypeID, $documentID, $supplierID)){
if(isset($GLOBALS["zip"])){
// Si le zip est bon on l'envoi a ATAK
sendToAtak();
// Si le retour est OK on affiche le HTML retourne par ATAK
echo $GLOBALS["htmlReturnFromATAK"];
}
}
}
break;
}
}
?>
CSS
.loader{
width: 25px;
height: 25px;
border-radius: 100%;
position: relative;
margin: 0 auto;
}
/* LOADER 1 */
#loader-1:before, #loader-1:after{
content: "";
position: absolute;
top: -10px;
left: -10px;
width: 100%;
height: 100%;
border-radius: 100%;
border: 10px solid transparent;
border-top-color: #3498db;
}
#loader-1:before{
z-index: 100;
animation: spin 1s infinite;
}
#loader-1:after{
border: 10px solid #ccc;
}
@keyframes spin{
0%{
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100%{
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.center {
display: flex;
justify-content: center;
align-items: center;
}