Import de fichier pour remplir un objet métier

Bonjour,

J’ai besoin de réaliser une fonctionnalité qui vide une table de 4 colonnes et la rempli à partir d’un fichier CSV. J’ai créé mon objet métier puis j’ai utilisé un adapter (dans exploitation) pour tester. Ça fonctionne bien sous réserve que je fasse le matching de chaque colonne avec mes champs d’objet métier.

Après avoir lu la doc mais surtout le forum, j’ai compris que pour rendre ma fonctionnalité accessible à des utilisateurs de mon application, il fallait faire utiliser mon adapter par l’intermédiaire d’un external object. Mais je ne comprend pas bien les concepts de chaque morceau de simplicité que j’utilise et je ne suis pas sûr de bien faire les choses correctement.

Merci de votre aide

Un “objet externe” est un composant (UI,; API, …) spécifique.

Il est donc possible, mais pas obligatoire, d’implémenter une page de saisie d’un import (via adapter ou sans) pour rendre les choses plus simples et plus “cadrées” pour des utilisateur finaux (vs les pages d’import standard XML et CSV qui sont plus souples mais plus compliquées à utiliser)

Selon le besoin il peut y avoir d’autres approches (ex: un attribut fichier non persistant d’un objet métier dédié + l’invocation de l’adapter dans le postSave)

Je serai partant pour l’approche la plus Simplicité-friendly, donc pas l’objet externe si je comprend bien et plutôt ce qui est le plus natif possible.

Quel est le besoin exactement ?

Un simple upload de fichier (pour le charger via un adaptateur particulier) ou y a t’il d’autres choses à gérer ?

J’ai un objet métier avec 4 champs, j’ai un fichier CSV avec 4 colonnes, je veux que l’utilisateur puisse importer son fichier pour vider toutes les données de l’objet métier puis insérer les données du CSV dans l’objet métier.

Bonjour,

Vous pouvez créer

  • une Action de liste sur votre objet en lui ajoutant un champ de type Document et une méthode.
  • l’habiliter à ceux qui peuvent importer un fichier

Ensuite en back vous pouvez récupérer le fichier posté

public String myCustomAction(Action action) {
	DocumentDB myDoc = action!=null ? action.get("myDocField").getDocument() : null;
	File file = myDoc!=null ? myDoc.getUploadFile() : null;
	// do something with the document...
	// adapter...
	// Remove the file from /tmp directory
	if (file!=null) file.delete();
}

L’import par adapter s’appelant de cette manière :

Message res = new Integration().importADP(getGrant(), "MyAdapter", new FileInputStream(file), "My custom import origin", null);

public Message importADP(Grant g, String adp, InputStream in, String origin, Map<String,Object> params)

“My custom import origin” se retrouvera dans le menu Exploitation / supervision des imports.

Oui ça peut être aussi une action de l’objet.

Une question : quand vous dites “vider toutes les données de l’objet métier puis insérer” êtes vous certain que ça soit la bonne approche ?

En effet si d’autres objet référencent des records existants, les supprimer pusi les recréer va “casser” ces liens.

Dans Simplicité les mises à jour de données de référence, si on parle de ça ici, se font plutôt en mode “upsert” = insert or update, quitte à gérer un flag de suppression logique

Je cherche à remplacer une fonctionnalité existante qui ne s’appuie pas sur les adapters (et donc aucun suivi des imports possible) par quelque chose de natif. Et le code actuellement fait littéralement une suppression total avant un import des données du fichier. Donc je garde la même approche pour l’instant.

Je vais tenter de mettre en place la solution proposée, merci à tout les deux

Un import via adapter est dument tracé dans le suivi des imports, ex:

Si vous devez rendre cette supervision accessible à des utilisateurs finaux, il convient en général d’en configurer un hériter simplifié/filtré

Oui c’est tout l’objectif de cette bascule! Merci

Ah oui désolé j’avais mal lu “ne s’appuie pas sur les adapters”.

Petit retour d’expérience. Pour imager le résultat, disons que l’objectif est d’importer un CSV qui va remplir un objet métier “Personne” avec le nom et prénom de chaque personne.

J’ai créé un premier objet métier “Personne” avec les deux attributs qu’il faut.
Ensuite j’ai créé un second objet métier “ImportCsvPersonne” avec les attributs fichier, dateCreationImport, dateDebutImport, dureeImport, nombreAjout.

Sur mon objet ImportCsvPersonne, j’ai créé une action qui lance une méthode dans mon objet métier afin de démarrer un new Thread et de lancer mon import en arrière plan. A la fin, je met à jour la date de fin de l’import et la progression arbitrairement à 100.

J’ai créé un Adapter qui extend de CSVLineBasedAdapter qui consiste simplement à sauter la première ligne (le header) et pour les autres, de créer un objet métier personne et d’y ajouter les valeurs => ObjectDB personne = getGrant().getTmpObject(“Personne”) puis je fais des setFieldValue

De manière générale, j’ai réussi à faire ce qu’il fallait. Mais pour aller plus loin…voici mes questions :

Une fois que j’ai executé mon adapteur a partir de mon fichier via New Integration() comme le montre votre exemple, je ne comprend pas comment récupérer les résultats de l’adapter en lui-même (nombre de ligne ajouté, durée, etc.) : de pouvoir profiter de la puissance de l’adapter enfaite.

Dans l’écran de visualisation des imports dans Monitoring, tous mes imports restent à “A importer” et ne sont pas sur terminé

Je n’arrive pas a gérer la progression de l’import pour pouvoir l’afficher à l’utilisateur

Dans mon adapteur, je n’arrive pas a comprendre comment gerer le header (vérifier qu’il y a bien les colonnes que je veux lire, plutôt que de faire des valeurs, mais récupérer l’index des colonnes par rapport à leur nom.

Merci pour votre aide!

@Override
	public String processValues(long numeroLigne, String[] valeurs) throws PlatformException, InterruptedException {
		if (numeroLigne == 1)
			return "";

		ObjectDB personne = getGrant().getTmpObject("Personne");
		personne.setFieldValue("prenom", valeurs[0]);
		personne.setFieldValue("nom", valeurs[1]);
		personne.create();
		return "";
	}
public String import(){
		setFieldValue("dateImport", Tool.getCurrentDateTime());
		save();

		String id = getGrant().simpleQuery("select max(row_id) from import;" );
		new Thread(() -> {
			try {
				processImport(id);
			} catch (Exception e) {
				AppLog.log("ERROR", getClass(), "import", e.getLocalizedMessage(), getGrant());
				AppLog.error(getClass(), "import", e.getLocalizedMessage(), e, getGrant());
			}
		}).start();
		return null;
	}

	public void processImport(String importId) throws IOException{
		LocalDateTime dateDebut = LocalDateTime.now();

		invokeMethod_Personne_deleteAllEntries();
		File fichierCsv = getField("fichier").getDocument(getGrant()).getFile();

		if (fichierCsv != null) {
			enregistreEtat("En cours...", importId);
		} else {
			enregistreEtat("Erreur", importId);
			return;
		}

		Message res = new Integration().importADP(getGrant(), "CSVAdapterImport", new FileInputStream(fichierCsv), "Import v2", null);

		enregistreEtat("Terminé", importfId);
		setFieldValue("dureeImport", calculDuree(dateDebut ));
		setFieldValue("progression", 100);
		update();
	}

Cet objet surabondant ne sert à rien, ce que proposait @Francois c’était, il me semble, d’ajouter une action avec attributs de confirmation (ici un attribut fichier) directement sur l’objet en question => ce type d’action ouvre une popup avec le formulaire de ces attributs additionnels utilisables coté back une fois confirmé

Exemple dans la démo:

Les résultats et logs de l’adapter sont dans le Message retourné par importADP (et se retrouvent aussi dans la supervision des imports), ex: Message.getResultLog()

Cet objet surabondant ne sert à rien, ce que proposait @Francois c’était, il me semble, d’ajouter une action avec attributs de confirmation (ici un attribut fichier) directement sur l’objet en question => ce type d’action ouvre une popup avec le formulaire de ces attributs additionnels utilisables coté back une fois confirmé

Enfaite l’objet est assez utile pour une vision purement utilisateur dans laquelle je rajoute quelques paramètres comme expliqué précédement (même si je ne peux pas tous les récupérer car il semble y avoir quelques bugs ou une mauvaise utilisation de ma part).

Par contre, Message.getResultLog() (ni aucune autre méthode de Message) ne fonctionne pour récupérer des résultats car mon adapter apparait dans les imports comme en cours pendant le traitement et bascule a “a importer” à la fin alors que le traitement est bien terminé. Et la, le code est simple, j’utilise exactement la fonction que vous m’avez donné.

screen_1

L’adapter a ses propres appenders en fichier, tout dépend du type d’adapter.
(ensuite les fichiers sont visibles depuis la supervision des imports).

Quand il est invoqué par new Integration().importADP, il y a juste un raiseInfo("Traité sans erreur") ou un raiseWarning(type d'erreur) (cf la liste de valeur XSP_STATUS) qui est remonté à l’appelant/à la UI en fin de traitement.

Il n’est pas question de remonter en mémoire ou a l’écran les fichiers de logs/erreurs de l’adapter lui-même car ils peuvent être énormes et ne sont pas dédiés à un traitement synchrone/UI.

Vous pouvez récupérer le row_id de la supervision pour aller chercher les fichiers ou faire un redirect sur l’écran de supervision result.get(Integration.RESULT_XSP_ID), l’utilisateur doit télécharger les logs.

Pour remonter des infos utilisateur, il est préférable de maitriser la volumétrie remontée au front.
Par exemple plutot que de remonter toutes les lignes en erreur, vous pouvez remonter le nombre de lignes rejetées ou une synthèse des erreurs rencontrées (en utilisant un paramètre de l’objet ou des Grant pour écrire puis lire ce paramètre spécifique au besoin).

Par exemple :

ArrayList<String> msg = new ArrayList<String>();
getGrant().setParameter("MY_ADP_MSG", msg);
new Integration().importADP...
String r = String.join(",", msg);
getGrant().removeParameter("MY_ADP_MSG");
return Message.formatSimpleText(r);

Et dans l’adapter :

ArrayList<String> msg = (ArrayList) getGrant().getObjectParameter("MY_ADP_MSG");
if (msg!=null) msg.add("Un message pour le front");

Le statut “for Import” indique que votre adapter n’est pas direct, mais est sensé produire un format XML Simplicité à importer (il alimente surement un output file qui devrait s’importer à la fin s’il est en XML).

La création de thread est à proscrire. Pour créer une tache asynchrone, il faut utiliser JobQueue.push("myJobName", new Runnable...) pour empiler la tache dans un pool de threads prévu à cet effet.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.