Commit 3c9136ff authored by Your Name's avatar Your Name
Browse files

init

parent 52906048
# CD78 — Capture Kofax (kofaxToMC)
Le code sur cette branche sert à injecter dans Maarch Courrier les lots fournis par la numérisation via Kofax.
Le script se lance dans une console avec la commande :
```bash
bash kofax_capture.sh
```
## À faire
* envoi de notifications par mail dans `php/notify.php`
## Fait
* bloquer les lots à la moindre erreur et les déplacer dans un dossier `dossierErreur/error_XXX/dir/subdir/` avec `XXX` le code de l’erreur
* fait
* définir s’il faut supprimer ou déplacer les fichiers traités pour éviter de les traiter à chaque lancement du script
* déplacement mis en place --> à tester --> test ok
* commentaire (champs xml `/Root/Comments`) en annotation du courrier
* fait
## À configurer pour le client
* MAARCH_WS_USER username de webservices
* MAARCH_WS_PASS password de webservices
* MAARCH_WS_URL url de webservices
* droits et périmètre de cet utilisateur
* autoriser les formats xml et ers pour les pièces-jointes
* NOTIFY_EMAIL email à notifier en cas d’erreur
## Notes
### xml:
* DirectorateName: destination (entité traitante)
* documents: nom.pdf/nom.pdf.ers/[hash]#nom2.pdf/nom2.pdf.ers/[hash2]#
* documentCount: nombre de fichiers
* Comments: annotation (optionnel)
* BatchReceptionDate: date de réception (optionnel)
### contrôle du hashage:
* il s’agit du hash des PDF
* TimeStampHashAlgorithm: algo de hashage
* hash dans /documents
### arborescence
* dossiers "LOT": un par direction (entité)
* sous-dossiers "PLI": un par courrier + PJs + ers + xml
* dans le code, les **chemins d’accès aux fichiers** doivent être paramétrables facilement, selon l’arborescence
mettre nom fichier principal dans champ custom
<?xml version="1.0" encoding="utf-8"?>
<root>
<config>
<docnamefield>11</docnamefield>
<maindocconstfield>10</maindocconstfield>
<archiveboxidfield>12</archiveboxidfield>
<doctype></doctype>
<rest>
<username>cchaplin</username>
<password>maarch</password>
<https>false</https>
<path>172.20.3.133/MaarchCourrier/cs_CD78_Recette/rest</path>
</rest>
<directories>
<source>/opt/maarch/kofaxToMC/partage</source>
<done>done</done>
<errors>errors</errors>
</directories>
</config>
</root>
Le hash d’un fichier de pièce-jointe a échoué. Fichier en question :
:docPath:
Ce fichier ainsi que l’ensemble du lot :batchName: a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Merci de vérifier l’intégrité du hash du fichier.
Hash cible : :hash:
Hash actuel : :fh:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Un fichier de pièce-jointe (PDF ou ERS) est introuvable.
Chemins indiqués :
:docPath:
:ersPath:
Ce fichier ainsi que l’ensemble du lot :batchName: a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Merci de déposer les fichiers aux bons emplacements, ou vérifier les noms de fichiers potentiellement existants.
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Une entité inexistante a été requise lors de l’import du lot :batchName:. Entité en question :
:destination:
L’ensemble des fichiers de ce lot a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Merci de corriger ceci dans le champ DirectorateName du fichier XML présent dans ce dossier.
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Une erreur est survenue lors de l’import dans Maarch Courrier d’un fichier ERS du lot :batchName:. Fichier en question :
:ersPath:
Ce fichier ainsi que l’ensemble du lot :batchName: a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Erreur :
:wserrors:
Merci de vérifier la configuration du module d’entrée dans le fichier :configPath:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Une erreur est survenue lors de la création du dossier de sauvegarde du lot :batchName:. Il y a un risque de création de doublon.
Merci de vérifier les droits de l’utilisateur maarch sur le dossier :destDir:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Une erreur est survenue lors de la sauvegarde du lot :batchName:. Il y a un risque de création de doublon.
Merci de vérifier les droits de l’utilisateur maarch sur le dossier :destDir:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Une erreur est survenue lors de l’ajout de note au lot :batchName:.
L’ensemble de ce lot a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Erreur :
:wserrors:
Merci de vérifier la configuration du module d’entrée dans le fichier :configPath:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Une erreur est survenue lors de l’import dans Maarch Courrier d’un fichier PDF du lot :batchName:. Fichier en question :
:docPath:
Ce fichier ainsi que l’ensemble du lot :batchName: a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Erreur :
:wserrors:
Merci de vérifier la configuration du module d’entrée dans le fichier :configPath:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Le hash du fichier principal du lot a échoué. Fichier en question :
:docPath:
Ce fichier ainsi que l’ensemble du lot :batchName: a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Merci de vérifier l’intégrité du hash du fichier.
Hash cible : :hash:
Hash actuel : :fh:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Le fichier PDF ou ERS du courrier principal est introuvable.
Chemins indiqués :
:docPath:
:ersPath:
Ce fichier ainsi que l’ensemble du lot :batchName: a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Merci de déposer les fichiers aux bons emplacements, ou vérifier les noms de fichiers potentiellement existants.
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Une erreur est survenue lors de l’import dans Maarch Courrier du fichier principal du lot :batchName:. Fichier en question :
:docPath:
Ce fichier ainsi que l’ensemble du lot :batchName: a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Erreur :
:wserrors:
Merci de vérifier la configuration du module d’entrée dans le fichier :configPath:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
Une erreur est survenue lors de l’import dans Maarch Courrier du fichier XML du lot :batchName:. Fichier en question :
:xmlPath:
Ce fichier ainsi que l’ensemble du lot :batchName: a été déplacé vers :errorDir:/:errorCode:/:batchPathInRootDir:
Erreur :
:wserrors:
Merci de vérifier la configuration du module d’entrée dans le fichier :configPath:
Une fois l’anomalie corrigée, merci de remettre en place le lot à capturer avec la commande :
mv :errorDir:/:errorCode:/:batchPathInRootDir: :rootDir:/:batchPathInRootDir:
Ils seront ensuite capturés de nouveau automatiquement.
#!/bin/bash
cd "$(dirname "$0")"
logFile="/opt/maarch/kofaxToMC/partage/LogsKofaxToMC/kofax_capture_$(date +"%Y%m%d-%H%M%S").log"
{ time php php/main.php config.xml ; } 2>&1 | tee "${logFile}"
[ "$(wc -l < "${logFile}")" -eq 1 ] && rm -f "${logFile}"
echo 1 > /opt/maarch/kofaxToMC/partage/LogsKofaxToMC/kofax_capture.log
<?php
function curlRequest($body = null,$url,$auth,$method) {
$req = [
'url' => $url,
'method' => $method,
];
if ($body !== null) {
$req['body'] = json_encode($body);
$req['headers'] = ['content-type:application/json'];
}
if (!empty($auth)) {
$req['basicAuth'] = $auth;
}
$res = execSimple($req);
if (!empty($res['errors'])) {
return ['errors' => $res['errors']];
}
return $res['response'];
}
function execSimple(array $args) {
$opts = [CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_SSL_VERIFYPEER => false];
//Headers
if (!empty($args['headers'])) {
$opts[CURLOPT_HTTPHEADER] = $args['headers'];
}
//Auth
if (!empty($args['basicAuth'])) {
$opts[CURLOPT_HTTPHEADER][] = 'Authorization: Basic ' . base64_encode($args['basicAuth']['user']. ':' .$args['basicAuth']['password']);
}
if (!empty($args['bearerAuth'])) {
$opts[CURLOPT_HTTPHEADER][] = 'Authorization: Bearer ' . $args['bearerAuth']['token'];
}
//QueryParams
if (!empty($args['queryParams'])) {
$args['url'] .= '?';
$i = 0;
foreach ($args['queryParams'] as $queryKey => $queryParam) {
if ($i > 0) {
$args['url'] .= '&';
}
$args['url'] .= "{$queryKey}={$queryParam}";
++$i;
}
}
//Body
if (!empty($args['body'])) {
$opts[CURLOPT_POSTFIELDS] = $args['body'];
}
//Method
if ($args['method'] == 'POST') {
$opts[CURLOPT_POST] = true;
} elseif ($args['method'] == 'PUT' || $args['method'] == 'PATCH' || $args['method'] == 'DELETE') {
$opts[CURLOPT_CUSTOMREQUEST] = $args['method'];
}
//Url
$opts[CURLOPT_URL] = $args['url'];
$curl = curl_init();
curl_setopt_array($curl, $opts);
$rawResponse = curl_exec($curl);
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
$errors = curl_error($curl);
curl_close($curl);
$headers = substr($rawResponse, 0, $headerSize);
$headers = explode("\r\n", $headers);
$response = substr($rawResponse, $headerSize);
return ['code' => $code, 'headers' => $headers, 'response' => json_decode($response), 'errors' => $errors];
}
<?php
/* kofaxToMC: capture dans MaarchCourrier des documents scannés par Kofax
* @author Quentin RIBAC pour Maarch
*
* usage: php php/main.php dossier/
*/
require 'curlRequest_function.php';
echo "--- kofaxToMC ---\n";
// lecture du fichier de configuration
$configPath = (count($argv) > 1 && is_file($argv[1])) ? $argv[1] : './config.xml';
$configXml = simplexml_load_file($configPath);
$MAARCH_WS_USER = (string) $configXml->xpath('/root/config/rest/username')[0];
$MAARCH_WS_PASS = (string) $configXml->xpath('/root/config/rest/password')[0];
$MAARCH_WS_HTTPS = ((string) $configXml->xpath('/root/config/rest/https')[0]) === 'true';
$MAARCH_WS_PATH = (string) $configXml->xpath('/root/config/rest/path')[0];
$MAARCH_WS_URL = 'http'.($MAARCH_WS_HTTPS ? 's' : '').'://'.$MAARCH_WS_USER.':'.$MAARCH_WS_PASS.'@'.$MAARCH_WS_PATH;
$docnamefield = (string) $configXml->xpath('/root/config/docnamefield')[0];
$maindocconstfield = (string) $configXml->xpath('/root/config/maindocconstfield')[0];
$archiveboxidfield = (string) $configXml->xpath('/root/config/archiveboxidfield')[0];
$rootDir = (string) $configXml->xpath('/root/config/directories/source')[0];
$doneDir = (string) $configXml->xpath('/root/config/directories/done')[0];
$errorDir = (string) $configXml->xpath('/root/config/directories/errors')[0];
$doctype = (string) $configXml->xpath('/root/config/doctype')[0];
$rootDir = rtrim($rootDir, '/');
$doneDir = rtrim($doneDir, '/');
$errorDir = rtrim($errorDir, '/');
$MAARCH_WS_URL = rtrim($MAARCH_WS_URL, '/').'/';
if (!is_dir($rootDir)) {
die($rootDir.' n’est pas un dossier accessible. Abandon.');
}
// fonction de déplacement d’un fichier de $rootDir vers $destDir avec création si nécessaire d’arborescence
function moveToDir($oldPath, $rootDir, $destDir) {
$newPath = str_replace($rootDir, $rootDir.'/'.$destDir, $oldPath);
if (!is_dir(dirname($newPath)) && !mkdir(dirname($newPath), 0777, true)) {
$errorCode = 'mkdir_failed';
notify();
return false;
}
if (!rename($oldPath, $newPath)) {
$errorCode = 'mv_failed';
notify();
return false;
}
$oldDir = dirname($oldPath);
while (is_dir($oldDir) && scandir($oldDir) === ['.', '..'] && $oldDir !== $rootDir) {
rmdir($oldDir);
$oldDir = dirname($oldDir);
}
return true;
}
// gestion d’erreur : déplace les fichiers d’un lot vers le dossier d’erreur correspondant
function moveToErrorDir($oldPaths, $rootDir, $errorCode) {
global $errorDir;
foreach ($oldPaths as $oldPath) {
if (!moveToDir($oldPath, $rootDir, $errorDir.'/error_'.$errorCode)) {
return false;
}
}
return true;
}
// fonction de lecture récursive des sous-dossiers pour trouver les fichiers correspondants à une condition fournie en callback
function findInDirectoryRecursive($dir, $filter) {
global $doneDir;
global $errorDir;
if (!is_dir($dir)) {
return [];
}
$selectedFileNames = [];
foreach (scandir($dir) as $sub) {
if (in_array($sub, ['.', '..', $doneDir, $errorDir])) {
continue;
}
if (is_file($dir.'/'.$sub) && !!call_user_func($filter, $dir.'/'.$sub)) {
$selectedFileNames []= $dir.'/'.$sub;
} elseif (is_dir($dir.'/'.$sub)) {
array_push($selectedFileNames, ...findInDirectoryRecursive($dir.'/'.$sub, $filter));
}
}
return $selectedFileNames;
}
// recherche des *.xml dans $rootDir
$xmlPaths = findInDirectoryRecursive($rootDir, function($name) {
return 1 === preg_match('/^.+\.xml$/', $name);
});
// fonction d’envoi de notifications d’erreur
function notify() {
global $errorCode;
echo "\n\t/!\\ Erreur /!\\\n";
$errorMessage = str_expand_vars(file_get_contents('error_messages/'.$errorCode.'.txt'));
echo $errorMessage;
return $errorMessage;
}
// fonction de remplacement des :nomsDeVariables: par leur valeur
function str_expand_vars($subject) {
return preg_replace_callback('/:((\w|\d|_)+):/', function($matches) {
$_var_name = $matches[1];
global $$_var_name;
if (isset($$_var_name)) {
return $$_var_name;
}
return "-$_var_name-";
}, $subject);
}
// récupération des entités
$entities = curlRequest(null, $MAARCH_WS_URL.'entities', null, 'GET');
if (!isset($entities->entities)) {
$repr = var_export($entities, true);
file_put_contents($rootDir.'/ERREUR.txt', 'Impossible de récupérer la liste des entités Maarch Courrier. Abandon.'.PHP_EOL.$repr);
echo $repr.PHP_EOL;
die('Impossible de récupérer la liste des entités Maarch Courrier. Abandon.');
}
$entities = $entities->entities;
// parcours des XML
foreach ($xmlPaths as $xmlI => $xmlPath) {
$batchName = basename($xmlPath, '.xml');
$batchPathInRootDir = str_replace($rootDir.'/', '', dirname($xmlPath));
$xml = simplexml_load_file($xmlPath);
echo "\n--- [".($xmlI+1).'/'.count($xmlPaths)."] $xmlPath ---\n";
$destination = (string) $xml->xpath('/Root/DirectorateName')[0];
$documentCount = (int) $xml->xpath('/Root/documentCount')[0];
$documents = explode('#', $xml->xpath('/Root/documents')[0]);
array_splice($documents, $documentCount);
$comments = (string) $xml->xpath('/Root/Comments')[0];
$arrivalDate = (string) $xml->xpath('/Root/ScanDateTime')[0];
$hashAlgo = (string) $xml->xpath('/Root/TimeStampHashAlgorithm')[0];
$archiveboxid = (string) $xml->xpath('/Root/ArchiveBoxID')[0];
$resId = null;
$errorCode = null;
$errorMessage = null;
foreach ($documents as $i => $document) {
$parts = explode('/', $document);
$docPath = dirname($xmlPath).'/'.$parts[0];
$ersPath = dirname($xmlPath).'/'.$parts[1];
if (!is_file($docPath) || !is_file($ersPath)) {
if ($i === 0) {
$errorCode = 'primary_file_not_found';
$errorMessage = notify();
break;
} else {
$errorCode = 'attachment_file_not_found';
$errorMessage = notify();
break;
}
}
$hash = strtolower($parts[2]);
$fh = strtolower(hash_file($hashAlgo, $docPath));
if ($hash !== $fh) {
echo "hash incorrect pour le fichier $docPath\n";
if ($i === 0) {
$errorCode = 'primary_file_hash_failed';
$errorMessage = notify();
break;
} else {
$errorCode = 'attachment_file_hash_failed';
$errorMessage = notify();
break;
}
}
echo "Hash correct pour : $docPath\n";
}
if (!empty($errorCode)) {
$paths = [$xmlPath];
file_put_contents(dirname($xmlPath).'/LISEZMOI.txt', $errorMessage);
$paths []= dirname($xmlPath).'/LISEZMOI.txt';
foreach ($documents as $document) {
$paths []= dirname($xmlPath).'/'.(explode('/', $document)[0]);
$paths []= dirname($xmlPath).'/'.(explode('/', $document)[1]);
}
moveToErrorDir($paths, $rootDir, $errorCode);
continue;
}
foreach ($documents as $i => $document) {
$parts = explode('/', $document);
$docPath = dirname($xmlPath).'/'.$parts[0];
$ersPath = dirname($xmlPath).'/'.$parts[1];
if ($i === 0) {
echo 'injection du document principal ... (PDF) ';
$destinationId = null;
foreach ($entities as $entity) {
if ($entity->entity_id === $destination) {
$destinationId = $entity->serialId;
break;
}
}
if ($destinationId === null) {
$errorCode = 'entity_not_found';
$errorMessage = notify();
break;
}
$arrivalDate = preg_replace('|^(\d{2})/(\d{2})/(\d{4}) (\d{2}:\d{2}:\d{2})$|', '$3-$2-$1T$4', $arrivalDate);
$resource = [
'modelId' => 1,
'chrono' => true,
'subject' => $batchName.' / '.$arrivalDate,
'status' => 'INIT',
'destination' => $destinationId,
'initiator' => $destinationId,
'arrivalDate' => $arrivalDate,
'encodedFile' => base64_encode(file_get_contents($docPath)),
'format' => 'pdf',
'customFields' => [
$docnamefield => $parts[0],
$maindocconstfield => 'Courrier entrant principal',
$archiveboxidfield => $archiveboxid,
],
];
$res = curlRequest($resource, $MAARCH_WS_URL.'resources', null, 'POST');
if (!empty($res->errors)) {
$wserrors = $res->errors;
$errorCode = 'primary_file_request_failed';
$errorMessage = notify();
break;
}
$resId = $res->resId;
echo '(ERS) ';
$attachment = [
'resIdMaster' => $resId,
'type' => 'kofax_ers_attachment',
'title' => basename($ersPath),
'encodedFile' => base64_encode(file_get_contents($ersPath)),
'format' => 'ers',
];
$res = curlRequest($attachment, $MAARCH_WS_URL.'attachments', null, 'POST');
if (!empty($res->errors)) {
$wserrors = $res->errors;
$errorCode = 'ers_attachment_request_failed';
$errorMessage = notify();
break;
}
if (!empty($comments)) {
echo '(note)';
$note = ['value' => $comments, 'resId' => $resId];
$res = curlRequest($note, $MAARCH_WS_URL.'notes', null, 'POST');
if (!empty($res->errors)) {
$wserrors = $res->errors;
$errorCode = 'note_request_failed';
$errorMessage = notify();
break;
}
}
echo "\n";
} else {
echo "injection de la pièce-jointe no $i ... ";
echo '(PDF) ';
$attachment = [
'resIdMaster' => $resId,