Ne pas persister un objet pour afficher le formulaire avec une action

Bonjour,

Je suis dans le scénarios suivant :

1 - un utilisateur affiche le formulaire de modification d’un user
2 - si il clique sur un bouton, en passant par une action il est redirigé vers un formulaire de création pré-remplis d’un autre BO ( un avenant à son contrat )
3 - si il clique enregistrer, cette objet est persisté

Le problème que je rencontre et que au moment de la redirection suite au clique sur le bouton, un avenant est créer dans la bas de donnée alors que cela ne devrait se produire que au moment de l’enregistrement.

Ci-dessous le code lié a l’action :

 public String openAvenantForm(){
       //recuperation de differentes data
        Avenant nouveau = (Avenant) getGrant().getTmpObject("Avenant ");
        //remplissage de 'nouveau' 
        nouveau.create();
        Parameters parameters = new Parameters();
        parameters.setParameter("nav","add");
        String url = HTMLTool.getFormURL(nouveau, nouveau.getRowId(), parameters.toJSON());

        return HTMLTool.redirectStatement(url);
    }

Bonjour,

Pour accéder à l’url de création d’un avenant, il faut utiliser
HTMLTool.getFormURL("Avenant", null, ObjectField.DEFAULT_ROW_ID, null);

ObjectField.DEFAULT_ROW_ID est la valeur par défaut du row_id pour la création.

Bonjour Nathalie,

Si je fait comme ca je ne passe pas d’informations au formulaire de création ( la partie //remplissage de nouveau dans mon exemple ) et je dois le faire.
Se sont des valeurs calculées et / ou extraites de la base qui doivent s’afficher comme valeur par défaut ou comme valeurs en lecture seulle.

J’ai l’impression que vous êtes en train de réinventer une fonction standard de la UI => si un objet B est lié à un objet A, alors quand je suis sur le formulaire d’un record A et que j’ai la liste des records B liés, quand je clique sur Créer sur cette liste le formulaire de création de B est affiché avec les infos de contexte issues de A.

Indépendamment de ce cas particulier écrire ce genre de code n’est pas bon:

Avenant nouveau = (Avenant) getGrant().getTmpObject("Avenant ");
nouveau.create();

En effet :

  1. vous zappez la partie validation ce qui n’est pas une bonne chose car cela revient à zapper tous les contrôles (obligatoirité, format, longueur, regex, …) des données
  2. vous ne gérez aucun retour d’erreur
  3. vous ne valorisez aucun attribut (ce qui peut peut être marcher dans certains cas particuliers mais pas dans le cas général)

En général il faut utiliser le wrapper BusinessObjectTool qui simplifie la gestion d’erreur et offre des verbes de plus haut niveau, ex:

ObjectDB obj = getGrant().getTmpObject("MyObject");
BusinessObjectTool objt = new BusinessObjectTool(obj);
try {
   objt.getForCreate();
   // do some stuff on obj
   objt.validateAndSave();
} catch(...)

Enfin dans 99% des cas il ne sert à rien de caster l’objet sur sa “vraie” classe, mieux vaut le manipuler en tant qu’ObjectDB. Si vous êtes obligés de caster c’est à priori que vous faites quelquechose de pas nominal

oui on a une demande du métier qui change de ce qui est fait d’habitude.

Dans notre model on a les liens suivant :
Jeune => contrat => avenant

La demande du métier est d’avoir un bouton dans le formulaire du jeune pour créer un avenant d’un type précis car les utilisateurs qui affiche la page du jeune n’ont les droits que sur ce type d’avenant en création.

Si je comprend bien la logique de simplicité ne permet pas de le faire en passant par une action qui redirige vers un formulaire sans trop rentrer dans le code personnalisé ?

Est-ce que l’on peut afficher le bouton de création d’un avenant sur le form du jeune sans afficher la liste des avenant ?
Ou encore est-ce qu’il est possible de faire pointer l’action sur une méthode de création des avenant et de le pré-remplir avec une méthode init* ?

C’est la première fois que nous avons ce genre de besoin sur notre application et j’aimerais le faire de la meilleur facon possible.

Vous pouvez configurer des actions avec paramètres si certains attributs ne peux pas être valorisés ou calculés automatiquement

Sinon de manière générale le code de votre action doit, cf. mon exemple de code:

  1. initialiser un record en vue de la création : getForCreate (NB: cela applique notamment les valeurs par défaut de attributs que vous avez paramétrées comme ayant une valeur par défaut)
  2. valoriser les attributs nécessaires : dans votre cas il faut, je pense au moins explicitement valoriser l’attribut foreign key qui fait pointer votre avenant sur le jeune
  3. valider et enregistrer : validateAndSave (NB: les attributs calculés se calculent avant le validate)

Le try/catch permet de récupérer et de remonter à l’utilisateur les éventuelles erreurs du validate et/ou de save, y compris celles générées par le code specifique que vous avez implémenté dans les hooks de l’objet

NB1: l’appel d’une action enregistre d’abord le record sur lequel on est.

NB2: si vous devez avoir des logiques différentes sur l’objet que vous crééz par l’action vs un objet que vous créer “manuellement” via formulaire, utilisez la mécanique des “instances” d’objets Simplicité pour distinguer les cas:

ObjectDB obj = getGrant().getObject("myCustomAction_MyObject", "MyObject");
...

Et dans les hooks de MyObject vous pouvez écrire des conditions du genre:

if ("myCustomAction_MyObject".equals(getInstanceName()) {
...
}

PS:

Sinon une autre approche dans votre cas est, en fonction du profil du user connecté, de jouer sur la valeur par défaut de l’attribut “type d’avenant” + rendre cet attribut non modifiable pour ce profil (par une contrainte statique ou du code ad hoc dans le postLoad de l’objet avenant).

Ainsi votre user ne pourra, via les mécanismes standards de création de la UI (donc sans action custom), de ne créer que ce type d’avenant.

Donc si je comprend bien on ne peut pas afficher un formulaire de création d’un autre objet sans persister en base de donnée cet objet au préalable si on souhaite pré-remplir le dit formulaire ?

On ne peut malheureusement pas, la valeur par défaut de ce champs est fixé sur une autre valeur.

La valeur par défaut peut être surchargée dans le postLoad pour un user donné

Pour rappel la définition statique (celle configurée) est juste la définition “par défaut”, lorsqu’un objet est instancé pour un user cette définition est clonée et peut être modifiée en tenant compte de choses spécifiques à ce user (typiquement ses groupes, et/ou un nom d’instance, etc.) Les différentes instances d’une même session utilisateur peuvent très bien avoir des définitions légèrement différentes.

Si je comprend bien votre besoin, certain types d’utilisateur ne peuvent créer que des avenant d’un type donné. Pour répondre à ça je mettrais par défaut l’attribut type d’avenant non modifiable et je le rendrait modifiable uniquement pour les profils qui ont le droit de créer des avenants de différents types. Ensuite s’il y a une subtilité sur le type je jouerais sur la valeur par défaut de cet attribut. Le tout dans la postload car c’est là qu’on met les règles statiques immuables pour un user donné.

Ex:

@Override
public void postLoad() {
    ObjectField f = getField("myField"); // Non updatable by default with "Default value" as default value
    if (getGrant().hasResponsibility("MY_GROUP1")) {
        f.setUpdatable(true);
    }
    if (getGrant().hasResponsibility("MY_GROUP1")) {
        f.setDefaultValue("Another default value");
    }
    ...
}

SI le besoin est d’avoir un bouton pour créer des avenants d’un type A et un autre bouton pour créer des avenants d’un type B mon approche serait: soit d’avoir des objets qui héritent d’avenant et qui contraignent l’attribut type les boutons d’action envoyant sur le formulaire de création de l’objet ad hoc

Ou alors je jouerai sur l’instance, chaque bouton envoie sur le formulaire de création de l’objet avenant mais avec un nom d’instance différent et dans mon postLoad je mettrais des règles différentes en fonction du nom d’instance:

@Override
public void postLoad() {
    ObjectField f = getField("myField"); // non updatable
    if ("type1_MyObject".equals(getInstanceName()) {
        f.setDefaultValue("type 1");
    } else if ("type2_MyObject".equals(getInstanceName()) {
        f.setDefaultValue("type 2");
    }
    ...
}

mais pour que mon instance créer dans la méthode d’action qui est dans le code de ma classe jeune soit recupérable dans le postLoad de la classe avenant je n’ai pas besoin de la persister en bdd ?

Sinon si je ne fournis que un nom d’instance a la méthode de redirection, comment je fais pour passer les valeur comme l’identifiant du jeune ( par exemple ) ?

Attention je ne voudrais pas qu’on mélange plusieurs choses

La notion d’ “instance” => dans Simplicité une instance est une instance de définition d’objet.

Une définition par défaut d’objet est chargée 1 fois dans ce qu’on appelle le core cache, elle est ensuite clonée dans ce qu’on appelle une “instance”, chaque clonage passe par le postLoad (qui est donc appelé 1 fois, et 1 seule fois, pour chaque instance).

On peut donc avoir plusieurs définitions d’objets au sein d’une même session utilisateur, avec des différences en fonction de l’usage qu’on veut en faire.

Ce qu’on met dans un postLoad c’est donc des règles statiques immuables liées à la manière dont on veut que se comporte un objet pour un user donné dans un type d’usage donné (NB: ce qu’on fait dans un postLoad correspond aux contraintes de type “statique”)

Si on parle de règles dynamiques variables (ex: règles qui dépendent du record qu’on consulte) c’est dans les hooks initXxx et/ou dans des contraintes front/back qu’il faut le faire.

Je reprécise tout ça car il y a souvent des contre sens sur le hook postLoad, on insiste lourdement sur ce point - et sur la notion d’instance qui va avec - en formation.

Mettez une trace dans le postLoad de votre objet pour bien voir à quel moment du cyle de vie il est appelé, ex: AppLog.info("Instance: " + getInstanceName(), getGrant());

Dans mes réponses précédentes où je parle d’instance je donne donc juste des pistes pour contraindre le type d’avenant en fonction du user connecté ou d’un contexte d’usage (via un bouton d’action dédié), c’est la première partie de votre besoin

L’autre partie de votre question concerne le fait que quand vous ouvrez un formulaire de création d’un objet B via une action d’un objet A vous voulez veut que les attributs issus de la foreign key qui pointe de B vers A soient pre-renseignés dans le formulaire de création (comme c’est le cas quand on clique sur le bouton “Créer” depuis la liste de B liée a A), là on parle de la notion de “parent object”. Je vais regarder comment gérer ça au mieux

Je me permet d’apporter mon grain de sel, c’est une chose qu’on a réussi à faire en JS:

  dateClick: function(info) {
                       jeunes.get(function(item) {
                        let itemAbs={
                            row_id:"0",
                            namAbsJenId__namNom:item.namNom,
                            namAbsJenId__namPrenom:item.namPrenom,
                            namAbsJenId__namRangHomo:item.namRangHomo,
                            namAbsJenId__namDateNaissance:item.namDateNaissance,
                            namAbsDate:moment(info.dateStr).format('YYYY-MM-DD'),
                            namAbsDateFin:moment(info.dateStr).format('YYYY-MM-DD'),
                            namAbsGroup:moment(info.dateStr).format('YYYY-MM-DD')
                        };

                        $ui.displayForm(null, "NamAbsence", "0", { nav: "add", values:itemAbs });
                    }, info.resource._resource.id);
       
            },

Cette méthode permet en utilisant FullCalendar de cliquer sur une date et d’ouvrir le formulaire de création d’une absence préremplie avec le user et les dates. Ne peut-on pas faire de la même manière en appelant le formulaire d’un BO et en lui passant des paramètres ?

Logiquement dans votre code il ne faudrait valoriser que la FK namAbsJenId, pas uniquement les attributs ramenés qui n’ont pas de sens sans la FK (les attributs ramenés sont populés depuis la FK pas l’inverse)

Mais sinon oui votre action peut être une action front qui appelle du JS custom (sauf que dans le cas dont on discute ici il y a une subtilité sur le nom d’instance c’est sur ça que je dois regarder de plus près) soit une action back qui renvoie du JS.

Je vais regarder un peu plus tard aujourd’hui quelle est la meilleure approche

Disons qu’on est sur le formulaire d’un record de l’objet MyObjA, et qu’on veut ouvrir, via une action custom, le formulaire de création d’un objet MyObjB (qui pointe sur MyObjA via la FK myFK) à la manière de ce qui se ferait quand on clique sur le bouton “Creer” du panel de cet objet (c’est à dire avec les infos du record parent prévalorisés), il faut faire renvoyer a son action un statement JS du genre:

return javascript(
  "var b = app.getBusinessObject('MyObjB', 'action3_ajax_MyObjB'); " +
  "$ui.displayForm(null, b, app.DEFAULT_ROW_ID, { parent: { name: obj.getName(), inst: obj.getInstanceName(), field: 'myFK', rowId: obj.getRowId(), object: obj } })"
);

NB: Dans ce contexte la variable obj correspond à l’objet MyObjA sur le record affiché.

ici j’utilise volontairement une instance specifique de mon objet MyObjB ce qui me permet d’implémenter des règles particulières, soit statiques (= dans le postLoad ou via des contraintes statiques) soit dynamiques (= dans le initCreate ou via des contraintes back) => dans votre cas il s’agit ici des règles qui forcent le type d’avenant