28 janvier 2020

Table of Contents

1. Introduction

Welcome to the Apache OFBiz developer manual. This manual provides information to help with customizing and developing OFBiz. If you are new to OFBiz and interested in learning how to use it, you may want to start with the "Apache OFBiz User Manual".

OFBiz is a large system composed of multiple subsystems. This manual attempts to introduce the overall architecture and high level concepts, followed by a detailed description of each subsystem. In addition, the manual will cover topics necessary for developers including the development environment, APIs, deployment, security, and so on.

1.1. Main systems

OFBiz at its core is a collection of systems:

  • A web server (Apache Tomcat)

  • A web MVC framework for routing and handling requests.

  • An entity engine to define, load and manipulate data.

  • A service engine to define and control business logic.

  • A widget system to draw and interact with a user interface.

On top of the above mentioned core systems, OFBiz provides:

  • A data model shared across most businesses defining things like orders, invoices, general ledgers, customers and so on.

  • A library of services that operate on the above mentioned data model such as "createBillingAccount" or "updateInvoice" and so on.

  • A collection of applications that provide a user interface to allow users to interact with the system. These applications usually operate on the existing data model and service library. Examples include the "Accounting Manager" and "Order Manager".

  • A collection of optional applications called "plugins" that extend basic functionality and is the main way to add custom logic to OFBiz.

1.2. Components

The basic unit in OFBiz is called "component". A component is at a minimum a folder with a file inside of it called "ofbiz-component.xml"

Every application in OFBiz is a component. For example, the order manager is a component, the accounting manager is also a component, and so on.

By convention, OFBiz components have the following main directory structure:

component-name-here/
├── config/              - Properties and translation labels (i18n)
├── data/                - XML data to load into the database
├── entitydef/           - Defined database entities
├── groovyScripts/       - A collection of scripts written in Groovy
├── minilang/            - A collection of scripts written in minilang (deprecated)
├── ofbiz-component.xml  - The OFBiz main component configuration file
├── servicedef           - Defined services.
├── src/
    ├── docs/            - component documentation source
    └── main/java/       - java source code
    └── test/java/       - java unit-tests
├── testdef              - Defined integration-tests
├── webapp               - One or more Java webapps including the control servlet
└── widget               - Screens, forms, menus and other widgets

It is apparent from the above directory structure that each OFBiz component is in fact a full application as it contains entities, data, services, user interface, routing, tests, and business logic.

Both core OFBiz applications as well as plugins are nothing more than components. The only difference is that core applications reside in the "applications" folder whereas plugins reside in the "plugins" folder; also OFBiz does not ship with plugins by default.

1.3. Example workflow

Many basic concepts were explained so far. An example would help in putting all of these concepts together to understand the bigger picture. Let us take an example where a user opens a web browser and enters a certain URL and hits the enter key. What happens? It turns out answering this question is not quite simple because lots of things occur the moment the user hits "enter".

To try to explain what happens, take a look at the below diagram. Do not worry if it is not fully understandable, we will go through most of it in our example.

ofbiz architecture

1.3.1. User enters URL

In the first step in our example, the user enters the following URL:

If we break down this URL, we identify the following parts:

  • localhost: Name of the server in which OFBiz is running

  • 8443: Default https port for OFBiz

  • accounting: web application name. A web application is something which is defined inside a component

  • control: Tells OFBiz to transfer routing to the control servlet

  • findInvoices: request name inside the control servlet

1.3.2. Control servlet takes over

The Java Servlet Container (tomcat) re-routes incoming requests through web.xml to a special OFBiz servlet called the control servlet. The control servlet for each OFBiz component is defined in controller.xml under the webapp folder.

The main configuration for routing happens in controller.xml. The purpose of this file is to map requests to responses.

Request Map

A request in the control servlet might contain the following information:

  • Define communication protocol (http or https) as well as whether authentication is required.

  • Fire up an event which could be either a piece of code (like a script) or a service.

  • Define a response to the request. A response could either be another request or a view map.

So in this example, the findInvoices request is mapped to a findInvoices view.

View Map

A view map maps a view name to a certain view-type and a certain location.

View types can be one of:

  • screen: A screen widget which translates to normal HTML.

  • screenfop: A PDF screen designed with Apache FOP based constructs.

  • screencsv: A comma separated value output report.

  • screenxml: An XML document.

  • simple-content; A special MIME content type (like binary files).

  • ftl: An HTML document generated directly from a FreeMarker template.

  • screenxls: An Excel spreadsheet.

In the findInvoices example, the view-map type is a normal screen which is mapped to the screen: component://accounting/widget/InvoiceScreens.xml#FindInvoices

1.3.3. Widget rendered

Once the screen location is identified and retrieved from the previous step, the OFBiz widget system starts to translate the XML definition of the screen to actual HTML output.

A screen is a collection of many different things and can include:

  • Other screens

  • Decorator screens

  • Conditional logic for hiding / showing parts of the screen

  • data preparation directives in the <action> tag

  • Forms

  • Menus

  • Trees

  • Platform specific code (like FreeMarker for HTML output)

  • Others (portals, images labels etc …​)

Continuing the example, the FindInvoices screen contains many details including two forms. One form is for entering invoice search fields and the other form displays search results.

1.4. Gestion de la documentation

Comment la documentation est organisé, avec quels outils est-elle réalisé, quels sont les conventions et toutes les autres questions concernant la gestion de la document et son élaboration sont traité dans un document dédié.

Il existe plusieurs documentations, chacune avec un objectif ou un lectorat cible, pour l’instant chacune est représenté par un fichier dans le répertoire racine .

2. Web Framework

3. Web Applications

The OFBiz webapp is one of the core framework components. It is tightly integrated with other framework components.

3.1. Cross-domains Single Sign On (SSO)

In some cases you need to split the OFBiz applications on different servers, and possibly in production on different domains. This can happen for different reasons, most often for performance reason.

As it’s annoying to give each time a credential when changing from an OFBiz application to another on the same server, the same applies when changing from an OFBiz application to another on another domain.

To prevent that on the same server, the ExternalLoginKey mechanism is used. The cross-domains SSO feature allows to navigate from a domain to another with automated SSO.

It based on 3 technologies:

JWT

JWT Official site - Wikipedia for JWT

CORS

CORS (Mozilla doc) - Wikipedia for CORS

Ajax

Ajax, now well known I guess, in OFBiz we use jQuery for that.

The mechanism is simple.

On the source side:
  1. When an user log in in an application (webApp) a webappName.securedLoginId cookie is created. This cookie will be used by the mechanism to know the current logged in user. Note that all webappName.securedLoginId cookies are deleted when the user session is closed or time out. Hence (apart also using an intrinsically secured cookie) the mechanim is secured, even on shared machines. Of course if people are sharing a machine during their sessions, things could get complicated. This unlikely later case is not taken in account.

  2. The user is given a JavaScript link which passes the URL to reach and the calling webapp name to the sendJWT() Ajax function.

  3. The sendJWT() Ajax function calls the loadJWT() Ajax function which in turn calls the CommonEvents::loadJWT method through the common controller.

  4. The CommonEvents::loadJWT method uses the calling webapp name to retrieve the userLoginId from the secured webappName.securedLoginId cookie, creates a JWT containing the userLoginId, and returns it to the loadJWT() Ajax function.

  5. Then the sendJWT() Ajax function sends an Authorization header containing the JWT to the URL to reach. At this stage, if all things are correct, the flow leaves the source side.

On the server side:
  1. A CORS policy is needed. Without it, the Authorization token containing the JWT will be rejected. It’s a simple policy but you need to strictly define the authorized domains. Never use the lazy "*" for domains (ie all domains), else the preflight request will not work. Here is an example for Apache HTTPD (domain value is "https://localhost:8443" for official OFBiz demo):

Header set Access-Control-Allow-Origin domain
Header set Access-Control-Allow-Headers "Authorization"
Header set Access-Control-Allow-Credentials "true"
  1. The checkJWTLogin preprocessor, similar to the checkExternalLoginKey, intercepts the JWT, checks it and if all is OK signs the user on. That’s it !

In the example component, the FormWidgetExamples screen contains 2 new fields in the LinksExampleForm which demonstrate the use from a local instance to the trunk demo instance.

If you are interested in more details you may refer to https://issues.apache.org/jira/browse/OFBIZ-10307

3.2. Control Servlet

3.2.1. Requests

3.2.2. Views

4. Entity Engine

4.1. Entities

4.1.1. Standard Entities

4.1.2. View Entities

4.1.3. Extended Entities

4.1.4. Dynamic View Entities

4.2. XML Data

4.3. Entity engine configuration

4.4. Supported databases

5. Service Engine

5.1. Declaration and Implementation

5.2. Supported languages

5.3. Transaction management

5.4. Web services

6. Widget System

6.1. Screen Widget

6.1.1. Decoration Pattern

6.2. Form Widget 2

6.3. Menu Widget

6.4. Tree Widget

6.5. Portal Widget

6.6. Platform Specific Code

7. FrontJs Portal

Le composant frontJsPortal, inclus dans le plugin du même nom, a pour objectif de fournir un moyen de gérer l’interface utilisateur avec une technologie de type FrontJs, c’est à dire VueJs, ou React ou Angular.

Il utilise, d’une part le systéme de portlet / portal de ofbiz, défini en XML, et un applicatif développé en javascript.

Pour l’instant ce composant est à l’état de POC (Proof Of Concept), c’est à dire que c’est un concrétisation de différentes idées afin de pouvoir en discuter plus facilement.
Il est développé dans un état d’esprit "Agile" c’est à dire que les résultats concrets sont privilégiés en acceptant de générer une dette technique.

La documentation qui suit a pour objectif,

  • d’une part d’expliquer où en est le composant et comment il fonctionne et quels sont les points choisis et ceux laissés de coté

  • d’autre part de préparer la futur documentation en détaillant les éléments à destination des futur utilisateurs

L’objectif majeur d’utiliser un framework javascript pour l’interface utilisateur est d’augmenter le nombre d’élément interactif dans les écrans, même quand les écrans sont construit / paramétré à partir de "module" existant en standard :

  • mettre à jours une partie de l’écran en fonction d’une action ou d’une mise à jours de donnée;

  • modification de formulaire en cours de saisi en fonction de la saisi des premmier champ;

  • paramétrage simple d’un écran (page portal) sans avoir à se préoccuper de l’interaction entre portlet.

7.1. POC Vuejs Portal

Actuellement le premier applicatif javascript permettant d’utiliser les portlets - portal Apache OFBiz est écrit avec le framework VueJs.

Il a été choisi pour sa simplicité d’apprentissage et en raison du pilotage de son développement par une communauté plutôt que par une société.

7.1.1. Installation POC VuejsPortal

  1. à partir d’un ofbiz-framework (trunk) téléchargé à partir du svn (documentation standard SourceRepository) une version svn supérieur à 1837232

  2. créer le répertoire plugins dans ofbiz-framework

  3. télécharger, avec git, dans le répertoire plugins :

    1. le plugin vuejsportal à partir du https://gitlab.ofbizextra.org/ofbizextra/ofbizplugins/vuejsPortal

    2. le plugin example (de Apache OFBiz) modifié pour les besoins du POC à partir de https://gitlab.ofbizextra.org/ofbizextra/ofbizplugins/example et en se mettant sur la branch vuejsPortal

  4. Il faut ensuite modifier quelques fichiers dans ofbiz-framework.
    Pour se faire, executer la commande suivante en étant positionné dans le repertoire 'plugins/VuejsPortal' :
    tools/applyOfbizFilesPatchs.sh
    puis ajouter quelques fichiers
    rsync -a --exclude-from=./ofbizFiles/rsyncExcludes ./ofbizFiles/ ../../

  5. Pour le build de l’applicatif vuejs il faut avoir nodejs installé sur son environement

    1. Pour installer node.js dans un environnement debian, executer le commandes suivantes :
      curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
      sudo apt-get install -y nodejs
      Dans le cas d’un autre environnement veuiller consulter la page https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions

    2. en attendant une commande intégré à gradle

      1. il faut se positionner à la racine de la webapp vuejs 'plugins/vuejsPortal/webapp/vuejsPortal'

        • npm i ⇐ pour charger toutes les dépendances

        • npm run build ⇐ pour builder

  6. il est maintenant possible de lancer ofbiz classiquement :
    ./gradlew cleanAll loadAll ofbiz
    ( à partir de la racine de ofbiz-framework )

  7. aller se connecter à l’application portal https://localhost:8443/exampleapi/control/main

7.1.2. Choix pour le POC

Usage de la branche trunk de Apache OFBiz.

Usage des principes et architecture de Portal Page et Portlet de Apache OFBiz.

L’ensemble des exemples d’usage sont avec le composant example et pendant la durée du POC même des nouveaux fichiers qui devrait être placé dans common sont mis dans example afin de simplifier l’installation et mise à jours.

Une webapp supplémentaire exampleapi a été créé pour centraliser les uri autorisé pour le composant vuejsPortal, c’est paramétré dans le fichier constantes.js.
Le composant vuejsPortal n’est pas spécifique à un composant, il necessite juste une uri de base pour ses requêtes. Cette uri est paramètré dans le fichier constantes.js pour l’instant, mais dans le futur le paramétrage sera plus souple afin de pouvoir utiliser vuejsPortal avec plusieurs composants, y compris si cela doit apparaitre comme plusieurs "webapp" dans le menu utilisateur.

Le composant portal ( https://localhost:8443/exampleapi/control/login ) utilise le méchanisme ofbiz standard de login via cookies, il pourra par la suite utiliser le login par token de ofbiz (cf https://issues.apache.org/jira/browse/OFBIZ-9833 ).

Les screens, forms, menus utilisés pour les portlets sont définis en xml, et dans des fichiers dédiés pour plus de lisibilité.

Il y a un composant (vuejs) par élément screen ofbiz ( SingleItemRow ⇒ vue-single-item-row ), qui sont défini via les renderer au niveau screen, form, menu (et pour les renderer html qui correspondent globalement au macro.ftl). Pour l’instant, pour le POC, pour avoir la liste, il faut faire une recherche sur les appels ouput.*Screen( .

Balise XML utilisé

Pendant le POC, trés peu de nouvelle property ou balise XML n’est ajouté mais certaine balise ou property sont détournés pour des usages VueJs exclusif.

  • screen

    • dans la balise <container, auto-update-target est utilisé pour le nom du watcher associé à ce container

    • dans la balise <container, id est utilisé comme identifiant de ce container permettant de lui injecter (par la suite) du contenu via un "setArea"

  • form

    • dans la balise <on-event-update-area (il peux y en avoir plusieurs, toutes seront executer dans l’ordre de présence dans le xml

      • event-type
        pour les 3 valeurs suivante, event-type permet de déterminer l’action a exécuter, lors du submit de la form (clic sur le button "submit")

        • setArea Mettre à jour l’area ayant l’identifiant areaId avec le retour (un get) de l’uri area-target et avec les éléments parameter inclus en tant que paramètre.

        • setWatcher Mettre à jour le watcher areaId avec le/s parameter inclus en tant que paramètre/s. Si aucun paramètre n’est renseigné, le watcher est mis à jour avec l’integralité des champs contenus dans la form.

        • submit il utilise la property area-target en tant que uri pour le post avec l’ensemble des champs de la form en tant que paramètre. <A-VERIFIER>Si area-target est vide alors il utilise la target de la form. Le retour de la requête est attendu avant d’enchainer sur le <on-event-update-area suivant si le retour est success, sinon le/s message/s d’erreur/s sont affiché/s.</A-VERIFIER>

    • dans la balise <on-field-event-update-area (il peux y en avoir plusieurs, toutes seront executer dans l’ordre de présence dans le xml

      • event-type permet de déterminer l’action a exécuter, lors du submit de la form (clic sur le button "submit")

        • post il utilisera la property area-target en tant que uri pour faire un post avec les éléments parameter inclus en tant que paramètre. <A-VERIFIER>Si area-target est vide alors il utilise la target de la form. Le retour de la requête est attendu avant d’enchainer sur le <on-event-update-area suivant si le retour est success, sinon le/s message/s d’erreur/s sont affiché/s.</A-VERIFIER>

        • setArea Mettre à jour l’area ayant l’identifiant areaId avec le retour (un get) de l’uri area-target et avec les éléments parameter inclus en tant que paramètre.

        • setWatcher Mettre à jour le watcher areaId avec le/s parameter inclus en tant que paramètre/s.

        • submit il utilise la property area-target en tant que uri pour faire un submit avec l’ensemble des champs de la form en tant que paramètre. <A-VERIFIER>Si area-target est vide alors il utilise la target de la form. </A-VERIFIER>

        • setFieldInForm il utilise la property area-target en tant que field au sein de la form area-id afin d’y attribuer la valeur du champs qui comporte l’instruction.

    • dans la balise …​-event-update-area

      • auto-parameters-portlet permet de générer l’ensemble des paramètres nécessaire aux portlet, c’est à dire

        • portalPageId

        • portalPortletId

        • portletSeqId

        • currentAreaId

    • dans la balise <hyperlink

      • link-type="anchor" est utilisé pour signifier que c’est un appel interne FrontJs

      • target-window est utilisé pour désigner le nom du watcher qui doit être mis à jours

  • menu

    • dans la balise <link

      • link-type="anchor" est utilisé pour signifier que c’est un appel interne FrontJs

      • target-window est utilisé pour désigner le nom du watcher qui doit être mis à jours

Renderer FrontJs

Un nouveau viewHandler ainsi qu’un nouvel ensemble de renderer a été créé.
Un nouveau package à été créé dans org.apache.ofbiz.widget.renderer frontJs qui contient l’ensemble des nouveaux renderer.

Dans ce package, il y a une class FrontJsOutput qui permet de construire les éléments nécessaires au format de sortie souhaité. Un objet de cette class est instancié en début de traitement du viewHandler, puis est compléter par les appels a chacun des objet renderer.

L’uri showPortletFj du composant exampleApi utilise le nouveau viewHandler pour retourner le resultat du rendu du screen, showPortlet.

cette uri est utilisé par l’applicatif vueJs pour toutes les requêtes de récupération des informations d’affichage.
Le viewHandler retourne, au format json deux ensemble (map) d’élément ( viewScreen et viewEntities), la première le/s affichage/s et la seconde pour les données.

Dans les futures versions, il sera surement faisable de ne souhaiter recevoir que la map de données.

ShowPortlet, ShowPortal

Le screen showPortlet est redéfini dans le POC (dans example/widget/example) par simplification, pour faire ce que l’on souhaite et seulement ça.

Il y a une uri showPortal dans l’exampleApi qui est dédié FrontJs.

Champ complémentaires

Ajout d’un champ dans l’entity PortalPagePortlet watcherName qui correspond au nom du watcher qui doit déclancher la mise à jour de la portlet

7.1.3. Situation actuel du POC

L’objectif est de faire fonctionner des pages portails correspondant à différent useCase.
Au niveau du menu, il y a les différentes page, pour l’instant le nom de la page portail est ExampleFrontJs et ExampleFrontJs2.
La première page contient 2 colonnes avec dans la première 2 portlets, le findExample puis le editExample et le listExample dans la seconde colonne.
La seconde page se rapproche de la page Mgmnt avec 2 portlets par colonne.

Pour chacune de ces portlet un composant portlet est créé dans le front avec le nom de la portlet et celui-ci s’initialise avec le showPortlet.

Le showPortletFj communique au composant la grappe de composants fils à créer ( ViewScreen ) et eventuellement le jeu de données nécessaire à son utilisation ( ViewEntities ).
Actuellement le format utilisé est json.

Imbrication des container
ViewScreen et ViewEntities

Actuellement tous les refresh d’écran (ou de portlet) font appel au FrontJsViewHandler qui renvoi la map ViewScreen et la map ViewEntities.
Dans une logique client FrontJs, la map ViewScreen devrait être reçu uniquement la première fois, contrairement à la map ViewEntities qui contient les données.

Actuellement, la gestion des "use-when", quelque soit le niveau (screen, form, field, menu) est réalisé (dans OFBiz) au niveau de la gestion du modèle donc cette information n’est pas accessible dans les rendrerer.
Dans le futur, il faudra transmettre au rendrer l’ensemble des éléments screens mais s’il y avait un use-when ajouter une données complémentaire boolean pour signaler qu’il faut l’afficher ou non. Ainsi, il sera possible lors des prochains appels à l’envoi de donnée de transmettre le/s boolean en question avec la/es valeurs correspondantes, afin de pouvoir mettre à jours correctement l’écran final.

7.1.4. Les TODO restant du POC

Mineurs
Important
Ces TODO sont plutot dans l’ordre de priorité. Les premièrs étant les plus prioritaire et sont sensés ne concerner que l’étapes majeur en cours (c’est à dire pas la suivante [smile o] )

Le screen showPortlet doit vérifier que les paramètre suivant sont bien présent, c’est nécessaire pour la lecture des attribues :

  • portalPageId

  • portalPortletId

  • portletSeqId


Le showPortlet doit vérifier le respect de la sécurité par rapport au deux champs SecurityServiceName et SecurityMainAction.

Majeurs
Important
Normalement, il ne faut pas démarrer deux évolutions majeurs en paralléle, mais l’une après l’autre
histoire d’avoir le temps de tester [smile o].

Dans la suite, les Notes correspondent à des évolutions qui sont encore en discussion.

Pouvoir choisir facilement si une portlet s’étend en hauteur ou en largeur. Cas d’usage: portlet search, soit sur une colonne soit en ligne.

A discuter, si la form doit inclure / géré les positions.

7.1.5. Les Cas d’Utilisation du POC

Opérationnels

Se logger sur l’application portal et afficher la page portail d’acceuil (ExampleFrontJs) Celle ci affiche les trois portlet findExample, listExample et editExample.

A mettre en oeuvre

Usage de la fonction recherche example
Test portlets FindExample and ListExample :

  1. search by name with contain (data-obj testContain), result: 11 lines (assert with message "testContain 12")

  2. search by name with BeginWith (data-obj testBegin), result: 35 lines (test with FindAndListExamples.checkTotalFound), so 2 pages test presence of nav-next (assert with message "Pb next button")

  3. search by id (data-obj testId), result: only one line and id is on list (assert with message "testId link")

  4. search by exampleType (data-obj testType), result: 25 lines and a next button (assert with message "Pb next button2") click on next-page and check there is 5 line print (assert with message "testType 5")

  5. search by name with contain (data-obj testContain1), result: less than 20 lines, so nav-next disappear, count number of line

  6. sort by description (data sortList.sort_0 ) via FindAndListExamples.sortListBy

  7. sort by name (data sortList.sort_1 ) via FindAndListExamples.sortListBy

  8. check number of line has not change and first line name equal sortList.result_1

  9. sort by id (data sortList.sort_2 ) via FindAndListExamples.sortListBy

  10. check number of line has not change and first line name equal sortList.result_2

7.1.6. Details sur Vue.js

Vue.js general

todo

Composants

Un composant est défini par 3 block distinct ( template, script, style ) qui sont rassemblés au sein d’un fichier '.vue'

  1. Le template :

    • Le template doit être contenu dans un élément unique ( div, span, table, ect…​ )

    • Dans le template on peut faire reference à des éléments de la partie script ( variables, fonctions ) grâce à l’aide de doubles accolades: {{}}

    • Les attributs html classiques ( id, type, class, style, ect…​) peuvent être précédé de ':' pour être lié aux données/fonctions du script ( ex: :class="data.class.alert" )

    • Des directives sont mises à disposition par Vue.js afin de faciliter la logique dans le template ( v-for, v-if, v-on:click, ect…​ ). Ces directives propres à Vue.js sont liées par default au context du script et n’ont pas besoin d’être précédées par ':'

  2. Le script :

    • La section script est principalement constituer d’un 'export default {}' qui va contenir l’ensemble des éléments du script.
      TIP: Cet export peut être précédé par des import du paquets node.js ou de fichiers ( image, script )

    • Cet export est un table de hash pouvant contenir les clefs data/method/computed/props

      • data() est un fonction qui contient les variables du composant.
        Toute variables declarées dans data() avant la création du composant ou rajouter grâce à la méthode Vue.set() sont réactives.
        C’est à dire que Vue.js va suivre les évolution de ses valeur et les répercuter partout où elle sont utilisées.

      • computed et une table de hash contenant uniquement des fonctions dont le resultat est évalué en fonction d’au moins une propriété réactive.
        Chacune de ces fonctions va être réévalué dès que une des propriété dont elle dépend est mise à jour.
        Ces fonction sont donc réactives et Vue.js va également répercuter ses changements à tout les endroits où elle est utilisée.

      • methods est une table de hash contenant des fonctions 'helper'.
        Elles peuvent être utilsées aussi bien dans le template que dans le script.
        WARNING: Ces fonctions ne seront jamais réévaluer automatiquement par Vue.js

      • props est un tableau de string qui defini quelles sont les paramètre que le composant peut recevoir de son parent.
        Elle sont utilisées dans le code sous la forme: 'this.props.nomDeLaProps'.

    • Cette section peut aussi contenir des hook existants dans le cycle de vie du composant ( created(), mounted(), beforeUpdate(), ect…​ ).
      Voir ci-dessous :
      Component lifecycle
      WARNING: Lors du hook created() les valeurs computed et les methodes du composant ne sont pas encore créer.

  3. Le style
    Cette section permet de definir le style du composant en css.
    Cette section sera prioritaire sur le style general du projet.

Vuex

Vuex est le systeme de centralisation de l’état de l’application. Il permet de créer des stores qui nous serviront à stocker des données accessible et modifiable à tout niveau de l’application.

Un store contient 4 élément :

  1. Le State
    Le state est une table de hash contenant les informations.
    Ce state ne peut être modifier que par des mutations.
    Les données présentes dans le state à l’initialisation de celui-ci seront réactive.
    Les données rajoutées après l’initialisation du store devront être crées à l’aide de la methode Vue.set() afin d’activer le suivi des modifications et de la rendre réactive.

  2. Les Mutations
    Les mutations sont une table de hash contenant les fonction capable de changer le state.
    Par convention la clef de ces fonction ne doit contenir que des majuscules.
    Les mutations ne peuvent pas être déclenchées directement à partir du code, elle doivent être appelées par une action.
    Les mutations doivent être synchrones.

  3. Les Actions
    Les actions servent à déclencher les mutations.
    A la manière d’un setter elles controlent que les nouvelles valeurs correspondent à ce que l’on attends.
    Une action peut être asynchrone, dans ce cas elle retourne une promesse.

  4. Les Getters
    Les getters permettent de consulter une/des valeur(s) du state.
    Les getters sont réactif.
    Les getters dans certain cas peuvent être paramétrés, dans ce cas ils retournent une fonction qui prendra des paramétres mais perdront leur faculté d’être réactifs. Ils seront réévalué seulement lorsque l’on rééxecuteras celui-ci ou qu’on en changera les paramétres.

Le store peut être composant de modules.
Dans ce cas à la racine du repertoire store on créé un fichier index.js que se charge d’importer Vuex et les stores que l’on placera dans un sous-repertoire modules.

Reactivity

todo

7.2. UI générique et modulaire

Afin d’avoir un ERP modulaire et ouvert, il est important que la gestion de l’interface Utilisateur permette, à partir d’un ensemble de "module" écran, de gérer de multiple écran pour gérer le même type d’objet métier dans différent type de contexte de l’entreprise.

Les chapitres suivants décrivent un POC pour un système de gestion de l’interface utilisateur afin de vérifier qu’il répond à ce besoin de modularité.

To be able to have a ERP modular and open, it’s imprtant that User Interface must allow, using a set of screen "block", to manage multiple screen to manage the same business object type in differents business context.

Chapter following, describes a POC for checking if a User Interface Management system meets this need for modularity.

7.2.1. Use cases for POC-UI

Theses use cases are to be used for new UI POC, documentation associated and selenium unit task test.

All these use cases should be done with existing entities and services, if it’s necessary to develop one, simplify the use case, the goal is UI, not service or entity.

These use case description are done in a agile philosophy, only main point is present and during realization details choices will be discuss and done.

Preliminary remarks :
  1. In this document, the term "application" corresponding to "plugin component" in the ofbiz terminology which is not same as a "applications/trunk component" in ofbiz terminology. An application is dedicated for a business purpose for a user type, it’s build by assembling piece of ofbiz components, sometimes without any specifics entities, services and screen (ex: CRM-B2C, CRM-B2B, SFA are 3 applications uses by sales men)

  2. Each use case is on an "application" and is part of one of the menu of this application.

    Of course, this document describe only menu-option needed by the use-case. As it’s difficult to do a clear definition of "web-page" because it’s depending of theme/template, use case is for me at two level :

    1. screen, which can be well define

    2. page, which depend on default theme

    Of course, some of use case (screen or page) will be done by a previous one (ex : sometime edit is done by the same screen as add). It’s even, one of the re-usable important point (UI, Doc and Selenium)

  3. Each time a line (or point) start by a "?" the question is "is this point is needed ?"

7.2.2. Release and goal

Goal is the POC realization, not doing multiple super applications. POC realization should generate discussion and decision, each time be careful to mainly discuss on UI, Doc or Selenium and not on use case business justification. Use case are to be realistic but mainly as a support for a specifics UI, Doc or Selenium needed.

V1

Main Goal is to proof the technical points. Of course UI re-organization is the driver for this phase, documentation start to work in parallel but will wait first realization to test integration for help. For selenium unit task, it will be a simple usage of the new UI with the default theme, the sole purpose being to check that test with default theme is simple.

So never mind if all cases is not done, the list exist to give different classic case what we will need to know how to process it. Some case which seem to be duplicate from other can be use for beginner who want to help and check if the concept is understanding.

During V1 realization, UseCase list will be updated

V2

Main Goal is to check if the solution is useful for the different type of contributors

  • experiment developer

  • beginner developer

  • functional consultant for parameters

  • functional consultant for personalization

  • ? end user - for parameters ? (if it’s still possible with the new architecture)

Use Case list will be use as a deployment plan. The list contains similar case to these which are realize in V1, so those on V2 can be achieve by all types of contributors.

Documentation Goal
  • Apache OFBiz User documentation (asscidoc)

  • Apache OFBiz web site wiki

  • OFBiz Help

Selenium test
  1. demo data for each use case and scenario

  2. selenium scenario test for each page (or page group)

  3. selenium unit test for each screen

7.2.3. Simple Party Management

Which Sub-Application
  1. in HR application, simple employee management

  2. in CRM B2C application, simple customer (person) management

  3. in eCommerce, simple profile page

  4. in HR application, simple organization/place (group) management

  5. in CRM B2B application, simple customer (company) management

  6. in Facility application, simple worker management

Which Party sub-component
  1. Party - Person - PartyGroup

  2. Contact Mech,

    • with postal address, phone and mail;

    • one or two fixes purpose (ex: phone fix number and mobile phone number)

  3. Role

  4. Party Identification

  5. Party association

  6. Not UserLogin because all Security entities should be use and it will generate a too large domain for this POC

Which Screen
  1. Party

    • find, list

    • A person

      • add / edit, show

    • A group

      • add / edit, show

    • A company

      • add / edit, show

    • show a synthesis view (party/person/company, contact informations, roles, Identification)

      • Person

      • Company

      • PartyGroup

  2. Contact information

    • all contact informations (for one party / facility)

      • with and without purpose

      • with and without history

      • deactivated

    • add / edit postal address

    • add / edit mail

    • add / edit phone

  3. Role

    • list for a party

    • add a role (for a parent RoleType)

    • add a role in two step :

  4. select parent RoleType

  5. select the role

    • remove a role

  6. Party Identifications

    • list, add, remove

7.2.4. HR Employee management

In HR Component, starting person management with the more complete form about person.

  • Menu option to manage employee

    • find, list, show, add, edit and manage his

      • contact information

      • identification (3 idTypes, one mandatory, two optionals)

  • template page with a header (or sidebar or …​) to show on which employee we are

Use Case Screen :
  1. find Person

    • simple form (only on party or person)

    • with an add button (which can be show or not depending on parameter or authorization)

  2. Person list with an add button (which can be show or not depending on parameter or authorization)

  3. add a Person

  4. show a Person

  5. show a Person with sub-menu with two options : contact informations and Identifications

  6. edit a Person

  7. List of all contact informations for a person, with an add button (which can be show or not depending on parameter or authorization)

  8. add a postal address

  9. add a mail

  10. add a phone number (to go step by step, without purpose management, will be done in next Use Case group)

  11. edit a postal address

  12. edit a mail

  13. edit a phone number

  14. List of all identification number for a person, with an add button (which can be show or not depending on parameter or authorization)

  15. add a identification number with choice of identification type

  16. edit a identification number with choice of identification type

  17. add a identification number with a fix identification type

  18. edit a identification number with a fix identification type

Use Case Page :
  1. create a person

  2. search a person

  3. visualize a person

  4. manage informations about a person

  5. template page with a header (or sidebar or …​) to show on which employee we are, (for example to show all his knowledges, or his skills, or his positions, or his …​)

  6. manage informations about a person on one page, and with access at this page directly by a field (auto-completion on id, first, last name)

7.2.5. CRM B2C, customer mgnt

In a CRM B2C application, the customer (so, in this context, a person) management.
The difference from previous use case group is :

  1. person form is more simple than in HR

  2. role will be used to characterize customer position (suspect, prospect, with_Quote, customer)

Menu option to manage employee

  • find (with role field), list, show, add, edit and manage his

    • contact informations

    • identification (3idTypes, one mandatory, two optionals)

  • template page with a header (or sidebar or …​) to show on which customer we are

Use Case Screen :
  1. find Person with an add button (which can be show or not depending on parameter or authorization)

    • search field same as in HR find person

    • role field which can appear or not, when not appear a fix value has been put as parameters.

    • contact information field, phone, mail, town. These fields can be show or not by the user with a "deploy" button

  2. Person list with an add button (which can be show or not depending on parameter or authorization)

    • role field appear or not, when not appear a fix value has been put as parameters, so only person with this role appear

  3. add a Person, all main informations in the form

    • role

    • less field about person than in HR form

    • 1 postal address

    • 2 phone number

    • 1 identification number

  4. show a Person, all main informations in the screen with indicator for contact information and identification when there are more data that what it’s show.

  5. show a Person with sub-menu with options :

    • contact informations

    • Identifications

    • role history

    • change role : a direct action button

  6. edit a Person, only "Person" field

  7. a button bar to change role (ex: for a suspect, there are the 3 options), this use case is for having a action bar, in this business process case it’s maybe not a need, but for more complex object like order or task, it’s a classical need.

  8. List of all contact informations for a person, with one or multiple add buttons (which can be show or not depending on parameter or authorization) and purpose are show, it’s the second step, with purpose management.

  9. add a postal address (or just a purpose)

  10. add a mail

  11. add a phone number

  12. edit a postal address

  13. edit a mail

  14. edit a phone number

  15. List of all identification number for a person, with an add button (which can be show or not depending on parameter or authorization)

  16. add a identification number with choice of identification type

  17. edit a identification number with choice of identification type

Use Case Page
  1. create a new entry in CRM (role is choose during creation)

  2. search a "customer" (or suspect, prospect, …​)

  3. visualize a "customer"

  4. manage informations about a "customer"

  5. template page with a header (or sidebar or …​) to show on which "customer" we are, (for example to show all his quotes, or his orders, or …​)

  6. manage informations about a person on one page, and with access at this page directly by a field (auto-completion on id, first, last name)

7.2.6. eCommerce, profile page

A simple profile page.
The difference from previous use case will be mainly on Use Case Page because eCommerce theme could be more original and public user interface should be, most of the time, more simple.

Use Case Screen :
  1. show the person, all main informations in the screen with indicator for contact information and identification when there are more data that what it’s show.

  2. show the Person with sub-menu with options :

    • contact informations

    • Identifications

  3. edit a Person, only "Person" field

  4. List of all contact informations for a person, with an add button and purpose are show, purpose is need for invoice or shipping.

  5. add a postal address (or just a purpose)

  6. add a mail

  7. add a phone number

  8. edit a postal address

  9. edit a mail

  10. edit a phone number

Use Case Page :
  1. visualize the profile (the person) with edit button

  2. manage his contact informations

  3. manage his identifications

  4. All in one page, which can be look as a long page.

7.2.7. HR organization mgnt

In HR component, a simple organization/place (group) management.
Now PartyGroup management (very simple), but with complex screen to manage hierarchy. In this use case group we will use the word "group" for service or department, or subsiadiry.

  • Menu option to manage the Company organization

    • manage group

    • associated employee in a group

    • manage a hierarchy of group

Use Case Screen :
  1. find group (with a specific partyType)

    • simple form (only on party or partyGroup)

    • with an add button (which can be show or not depending on parameter orauthorization)

  2. PartyGroup list with an add button (which can be show or not dependingon parameter or authorization)

  3. add a group

  4. show a Person, all informations in screen with sub-menu with two options : contact informations and Identifications

  5. edit a Group

  6. List all contact informations for a group, with an add button (which can be show or not depending on parameter or authorization)

  7. add a postal address

  8. add a phone number

  9. edit a postal address

  10. edit a phone number

  11. List all identification number for a group, with an add button (which can be show or not depending on parameter or authorization)

  12. add a identification number with choice of identification type

  13. edit a identification number with choice of identification type

  14. add a identification number with a fix identification type

  15. edit a identification number with a fix identification type

  16. List all person associated to the group with two add buttons (which can be, individually, show or not depending on parameter or authorization)

    • add a manager

    • add a member

  17. List all group associated to the group (the child) with two add buttons (which can be, individually, show or not depending on parameter or authorization)

    • add an existing group as a child

    • create a new group and add it as a child

    • in the list, each group is a link to this screen, to be able to navigate top-down

    • a third button to go to the parent level, to be able to navigate bottom-up

    • the name of the group manager appear above the list

  18. ? List all parent group for a group or for a person ?

  19. show group hierarchy as a tree with action or detail at each level, top-down

  20. show group hierarchy as a tree with action or detail at each level, bottom-up

Use Case Page :
  1. search a group

  2. manage a group

  3. manage its contact informations

  4. manage hierarchy step by step (parent to child or child to parent)

  5. manage hierarchy with a tree view

  6. in HR employee, show the tree, top-down or bottom-up with the template "for an employee"

7.2.8. CRM B2B customer mgnt

In a CRM B2B application, the customer (so, in this context, a company) management.
For clarification, in these Use Cases, B2B is an other application than B2C.
The "CRM B2C & B2B" will be a third, but not in this list because it contains no specificity on screen-page definition

The main difference between B2C is :

  1. company versus person,

  2. contact management with PartyAssociation

  3. ? customer organization management ?

Use Case Screen :
  1. find customer (a company (specific partyType)) with an add button (which can be show or not depending on parameter or authorization)

    • search field are on multiple entities with some part deploy or not

    • role field which can appear or not, when not appear a fix value has been put as parameters.

    • contact information field, phone, mail, town. These fields can be show or not by the user with a "deploy" button

  2. Company list with an add button (which can be show or not depending on parameter or authorization)

    • role field appear or not, when not appear a fix value has been put as parameters, so only company with this role appear

  3. add a Company, all main informations in the form

    • role

    • field from PartyGroup

    • 1 postal address

    • 2 phone number

    • 2 identification number

  4. show a Company, all main informations in the screen with indicator for contact informations and identification when there are more data that what it’s show.

  5. show a Company with sub-menu with options :

    • contact informations

    • Identifications

    • role history

    • change role : a direct action button

  6. edit a Company, only "Company" field

  7. a button bar to change role (ex: for a suspect, there are the 3 options), this use case is for having a action bar.
    In this business process case it’s maybe not a need, but for more complex object like order or task, it’s a classical need.

  8. List of all contact informations for a company, with an add button (which can be show or not depending on parameter or authorization) and purpose are show, (so, with purpose management).

  9. add a postal address (or just a purpose)

  10. add a mail

  11. add a phone number with purpose

  12. edit a postal address

  13. edit a mail

  14. edit a phone number

  15. List of all identification number for a company, with an add button (which can be show or not depending on parameter or authorization)

  16. add a identification number with choice of identification type

  17. edit a identification number with choice of identification type

  18. list of contact (person) associated to this company with an add button (which can be show or not depending on parameter or authorization)

    • a contact is a person with contact information

    • list with only one line per contact

    • list of block with contact details for each

  19. edit a contact or his contact information

Use Case Page :

Exactly the same as the CRMB2C

  1. create a new entry in CRM (role is choose during creation)

  2. search a "customer" (or suspect, prospect, …​)

  3. visualize a "customer"

  4. manage informations about a "customer"

  5. template page with a header (or sidebar or …​) to show on which "customer" we are, (for example to show all his quotes, or his orders, or …​)

  6. manage informations about a company on one page, and with access at this page directly by a field (auto-completion on id, first, last name).

7.2.9. Facility worker mgnt

In Facility application, simple facility’s worker management.
For this last use case group, it’s a simplification of the previous one.
Only a very simple and short process for adding people.

It’s the last one, because the goal is to check if it’s easy and rapid to create (or parametrize) a new small application from existing one.

In the Warehouse Management application (simple version OOTB)

  • in the administration menu

    • the user menu to manage internal user per facility In the standard business process, it will be used mainly for login and authorization, in our case we will only manage person, his phone number and his facility (where he’s authorized)

    • the facility menu to manage contact informations and person authorized

Use Case Screen :
Already existing screen used
  1. find Person

    • simple form (only on party or person)

    • with an add button

  2. Person list with an add button

  3. add a Person, simple form 3-6 fields

  4. show a Person

  5. show a Person with sub-menu with option to manage contact informations

  6. edit a Person

  7. List of all contact informations for a person, with one or multiple add button

  8. add a mail

  9. add a phone number

  10. edit a mail

  11. edit a phone number

New Screen
  1. add a facility, simple form, if service exist, including some contact informations

  2. List of all existing facility

  3. List of all contact informations for a facility, with one or multiple add button

  4. List of all persons associated to the facility, with two add button

    • add an existing person

    • create a new person and add it to the facility

  5. List of all facility associated to a person, with one add button

    • add an existing facility

Use Case Page :
  1. manage facilities

  2. manage persons

  3. visualize a facility details (info, contact informations, persons associated)

7.3. Portlet

Une portlet est une portion d’écran "autonome" c’est à dire qu’il peut y avoir des actions qui ne concerne que cette portion d’écran et qui sont déclanché par des éléments interne à elle.

Les portlets doivent permettre une grand modularité de l’interface utilisateur, aussi une action utilisateur (clic sur un lien, un bouton submit, …​) ne doit pas indiquer la portlet qui doit se mettre à jour mais un nom logique auquel pourront s’abonner une ou plusieurs portlet.

Ce nom logique auquel la portlet s’abonne est la watcherName, c’est un champ qui est dans la table d’association entre PortalPage te PortalPortlet

7.3.1. Mise à jour Portlet

C’est la mise à jours de donnée au niveau du store du client Js qui déclanche la mise à jours de la portlet

7.4. Portal Page

7.5. Dev and prod

7.5.1. message de retour

  • EVENT_MESSAGE_LIST : pour obtenir un event message list il faut; faire un creatExample ayant comme statusId="EXST_COMPLETE".

  • EVENT_MESSAGE : pour obtenir un EventMessage il faut; faire un creatExample ayant comme statusId="EXST_APPROVED".

  • ERROR_MESSAGE : pour obtenir un ErrorMessage il faut; que le exampleTypeId et que le description soient null.

  • ERROR_MESSAGE_LIST : pour obtenir un ErrorMessage list il faut; que le exampleTypeId soit null.

7.6. FrontJs Glossary

watchers

c’est nom du store VueJs utilisé pour stocker les différentes variable sur lequel les container ou portletsont abonné et donc se mette à jours quand celui-ci change.

watcherName

c’est le nom du champ (dans l’association Portlet - PortalPage) qui contient le nom de la variable dans watchers permettant de mettre à jours cette portlet dans cette PortalPage (cf Portlet .

Store entities

c’est le store qui stock l’ensemble des données utilisé sur la page portail, il est organisé par "record" de manière à pourvoir être utilisé aussi bien en tant que ligne de list que en tant que Screen Single.

8. Core APIs

9. Development environment

9.1. Setup your environment

9.1.1. Java SE

9.1.2. IDE

Eclipse
Intellij Idea

pour le hotswap, il faut importer (menu file project structure libraries clicker sur plus pour ajouter ofbiz-framework/build/libs/ofbiz.jar tous les modules (Ctrl A)

9.1.3. Database

9.2. Guide des développements dans OFBiz par l’exemple

January 24, 2013
revu complétement en juin 2018

9.2.1. Objectif de Example

Note
Transféré dans introduction du composant example
Note
La documentation de ofbiz est actuellement en train d’être ré-organisé, ce qui fait que l’aide contextuel n’est, momentanément plus maintenu et donc un peu ancienne.
Dans ce composant, vous pouvez lire l’aide de chaque écran, celle-ci vous expliquera quelle partie du développement est utilisé dans ce cas, mais si des explications vous semble en contradiction avec cette documention, c’est que ces explications ne sont plus valables.
Arborescence des fichiers sources
Important
cette section doit être transféré dans l’introduction de ce docuement

Au niveau de l’arborescence il y a plusieurs répertoires principaux :

ofbiz-framework/
├── applications/        - Les composants applicatifs majeurs
├── docs/                - Les fichiers root de chacune des documentations
├── framework/           - Les composants techniques
├── lib/                 - Vide au départ pour y mettre des lib complémentaires
├── plugins/             - Répertoire à créer pour y mettre tous les plugins, composants applicatifs complémentaires ou extention de composant existant
├── themes/              - Les différents thèmes disponible
├── runtime/             - Utilisés lors du fonctionnement
    ├── catalina/
    ├── data/            - les données de la base derby quand celle-ci est utilisé (paramétrage par défaut, utilisable pour des démos)
    ├── indexes/         - pour les indexe de Lucène (& solr)
    ├── logs/
    ├── output/          - utilisé quand il y a des fichiers uploadé via le composant content
    ├── tmp/             - idem ci-dessus
    └── tempfiles/

9.2.2. Comment développer dans OFBiz

Comment développer un nouvelle écran, personnaliser un existant, ajouter une table dans la base de donné, écrire un programme métier, …​

9.2.3. Pratiques de développement, Page portail et portlet

L’usage des pages portails et des portlets pour définir les interfaces utilisateurs est grandement recommandé.

Une page portail contient des colonnes et celle-ci contiennent des portlets, et ces portlets sont entièrement configurables : titre, disposition sur la page, menus, script,…​

De très nombreuse portlet existe déjà et dans bien des cas, lors de la création d’une page portail pour un nouveau besoin, il ne sera pas nécessaire de créer de nouvelle portlet, juste sélectionner les bonnes et les configurer.

Néanmoins, créer/développer une nouvelle portlet est simple, en effet un ensemble de portlet type a été défini pour répondre aux besoins les plus classiques lors du développement des interfaces.

Ainsi dans la plupart des cas, il suffit de définir un formulaire (une "Form") au bon endroit, dans le bon fichier, et la portlet est directement utilisable.

Cette partie a pour but de décrire les outils et les recommandations à disposition pour réaliser des interfaces standards et configurables dans les meilleurs délais.

Pour une meilleur vision des interactions entre les notions de PortalPage, Portlet, attribues, voici le diagram UML des données utilisés pour la gestion des PortalPages.

Diagramme UML des entités Portal
Les portlets

Il faut d’abord identifier le Types de la portlets.

Une fois son type sélectionné, il va falloir configurer la portlet en fonction des besoins de votre écran :

  • configuration du titre de la portlet

  • configuration du screen ou du form utilisé

  • utilisation d’un script avant le chargement de la portlet (récupération d’une liste, test de données, actions diverses)

  • utilisation d’un menu (en haut dans la barre de titre du screenlet)

  • utilisation d’un test sur une variable pour afficher ou non le contenu de la portlet

  • ...

La majorité des éléments configurables sur une portlet ont des valeurs par défaut qui évitent d’avoir à les définir lors de la création des portlets.

Ainsi, lorsqu’on regarde le fichier de définition des portlets ExamplePortletData.xml, très peu de champs sont renseignés, principalement ceux concernant la portlet elle-même.

  • configuration du titre de la portlet

  • configuration du screen ou du form utilisé

  • utilisation d’un script avant le chargement de la portlet (récupération d’une liste, test de données, actions diverses)

  • utilisation d’un menu (en haut dans la barre de titre du screenlet)

  • utilisation d’un test sur une variable pour afficher ou non le contenu de la portlet

  • ...

Et voici des extraits :

<PortalPortlet portalPortletId="FindExample"
   portletName="Find Example (with portletType and showPortlet)" description="portlet to define search criteria for Example list"
   portletTypeId="Screenlet"  component="example" subComponent="Example" helpName="HELP_Screenlet"/>
<PortalPortlet portalPortletId="ListExample"
   portletName="List Examples (with portletType and showPortlet)" description="portlet to list example depending on search criteria, this portlet is call by FindExample"
   portletTypeId="ScreenletList" component="example" subComponent="Example" helpName="HELP_ScreenletList"/>

Pour garantir la qualité de cette page portail, il est important de penser aux traductions (CommonPortalEntityLabels.xml) :

<property key="PortalPortlet.portletName.FindExample">
    <value xml:lang="en">Find Example </value>
    <value xml:lang="fr">Rechercher un exemple </value>
</property>
<property key="PortalPortlet.description.FindExample">
    <value xml:lang="en">portlet to define search criteria for Example list </value>
    <value xml:lang="fr">portlet (type Screenlet) de critères de recherche pour la liste des exemples </value>
</property>
Catégorie d’une portlet

Il est possible d’affecter une portlet à une ou plusieurs catégories, cela afin de permettre de rechercher plus facilement les portlets.

Habituellement, il y a une catégorie par composant ou par objet métier:: mais rien n’empêche dasn créer en fonction de besoin spécifiques. Lors de la création d’un catégorie il est possible de l’associer à une catégorie parente.

Ligne à ajouter dans le fichier data pour associer une catégorie à une portlet :

<PortletPortletCategory portalPortletId="FindExample2" portletCategoryId="EXAMPLE"/>
Description des champs définissant une portlet

Les différents champs de l’entité PortalPortlet définissent le fonctionnement des portlet. Ils sont listés ci-dessous.

Certains champs ont des valeurs par défaut (scriptName, menuName, screenName or formName), d’autres doivent être obligatoirement renseignés (l’identifiant, le type, le composant et sous-composant,…​).

Tip
Utilisez les valeurs par défaut

Avec les champs component et subcomponent renseignés, il y aura des valeurs par défaut pour beaucoup des autres champs. Il est trés fortement conseillé d’utiliser ces valeurs par défaut quand c’est possible

Tip
Retrouvez cette aide et plus, directement dans webtools

Lors de la réalisation d’une portlet, en cas de doute, il est possible de visualiser les valeurs par défaut généré, via la page Montrer les champs d’une portlet :

Fil d’ariane: Adm. Sys > DIVERS OUTILS DE CONFIGURATION (dans la page d’accueil) > Montrer les champs d’une portlet ou directement l’uri /webtools/control/ShowPortalPortlet

Dans cette page, en passant la souris sur le nom des champs, une description détaillée du champ apparait, ainsi que le détail de la construction de la valeur par défaut pour ce champ

Types de portlets

Pour faciliter la création de portlets, il existe plusieurs types de portlets correspondant chacune à un usage classsique dans des pages portail.

Ces types intègre une quantité d’objets et d’actions qui pourront être mis en oeuvre lors de la configuration de la portlet.

Table 1. Liste des différents types de portlet existants pour le moment
Type Description

Screenlet

type générique

ScreenletList

type générique pour une liste

Empty

contener vide pour permettre l’injection de portlet

Decorator

type permettant de charger la portlet via une URI dédié

Portlet de type Screenlet

C’est le type le plus courant de portlet.Il permet de gérer des screens ou des forms avec menu et zone d’édition (injection ou substitution).

On peut également éxécuter un script avant le chargement de la portlet pour récupérer entité, listes,…​

Lorsqu’on crée une nouvelle porlet, il faut définir un certain nombre de champs pour garantir son fonctionnement et sa réutilisation.

Il faut réfléchir à quel composant se rattache cette portlet et ses fonctionnalités. Une fois le composant et sous-composant identifiés, il faut decrire la fonction de la portlet du mieux possible.

Screenlet
Champs importants de l’entité PortalPortlet

Ensuite, on peut utilisés les champs complémentaires ou optionnels.

Tip
Utilisez les valeurs par défaut

Avec les champs component et subcomponent renseignés, il y aura des valeurs par défaut pour beaucoup des autres champs.

Champ de l’entité PortalPortlet utilisés par le type Screenlet (à renseigner uniquement si valeur spécifique)
Tip
Retrouvez cette aide et plus, directement dans webtools

Pour plus d’information voir l’aide détaillé des champs d’une portlet

Lors de la réalisation d’une portlet, en cas de doute, il est possible de visualiser les valeurs par défaut généré, via la page Montrer les champs d’une portlet :

Fil d’ariane : Adm. Sys > DIVERS OUTILS DE CONFIGURATION (dans la page d’accueil) > Montrer les champs d’une portlet ou directement l’uri /webtools/control/ShowPortalPortlet

Dans cette page, en passant la souris sur le nom des champs, une description détaillée du champ apparait, ainsi que le détail de la construction de la valeur par défaut pour ce champ

Les attributs génériques possible

Par défaut ce type de portlet est réductible avec save-collapsed="true"

  • collapsible : si vide c’est true non opérationnel : true pour le moment

  • saveCollapsed : si vide c’est true non opérationnel : true pour le moment

  • initiallyCollapsed : si vide c’est false

Portlet de type ScreenletList

Ce type de portlet est utilisé lorsque l’on a besoin d’une liste. En effet, les listes imposent, du fait du système de pagination, d’utiliser un form comme contenu. Ainsi il faut simplement créer le Form en respectant les règles de nommages.

Usage classique : gérer une liste d’enregistrement avec possibilité de les éditer.

Lorsqu’on crée une nouvelle porlet, il faut définir un certain nombre de champs pour garantir son fonctionnement et sa réutilisation.

Il faut réfléchir à quel composant se rattache cette portlet et ses fonctionnalités. Une fois le composant et sous-composant identifiés, il faut decrire la fonction de la portlet du mieux possible.

ScreenletList
Champ génériques de l’entité PortalPortlet
  • portalPortletId : identifiant de la portlet, max. 20 carac.

  • portletLongId : identifiant complémentaire, max. 60 carac.

  • portletTypeId : renseigner avec le type choisi, ici, ScreenletList

  • portletName : descrition courte, penser aux labels de traductions dans CommonPortalEntityLabels.xml

  • descrition : descrition longue, penser aux labels de traductions dans CommonPortalEntityLabels.xml

  • component : nom du composant, à renseigner pour générer les valeurs par défaut utilisées par le type de portlet

  • subComponent : nom du sous-composant, à renseigner pour générer les valeurs par défaut utilisées par le type de portlet

  • webapp : à renseigner lorsqu’on utilise des sous-répertoire dans le sous composant

  • screenshot : image png de la portlet

Ensuite, on peut utilisés les champs complémentaires ou optionnels.

Tip

Avec les champs component et subcomponent renseignés, il y aura des valeurs par défaut pour beaucoup des autres champs.

Champ de l’entité PortalPortlet utilisés par type Screenlet (à renseigner uniquement si valeur spécifique)
  • titleLabel : titre à afficher

  • formName : optionnel, dépend du champ useScreen, il est toujours utilisé

  • formLocation : voir ci-dessus

  • useScreen

  • screenName : optionnel, s’il est présent, il sera utilisé en plus de la form

  • screenLocation : voir ci-dessus

  • useMenu

  • menuName : ajout d’un menu en haut à droite de la screenlet, il est utilisé si useMenu est Y

  • menuLocation : voir ci-dessus

  • editAreaDivId : zone d’édition en haut du screen ou du form utilisé, premet d’injecter du contenu (via le menu, par exemple)

  • subAreaDivId : zone contenant le screen ou le form utilisé, permet de remplacer le contenu de cette zone (via le menu, par exemple)

  • useScript

  • scriptName, il est utilisé si useScript est Y

  • pkIdname

Par défaut ce type de portlet est réductible avec save-collapsed="true"

Tip

Pour plus d’information voir l’aide détaillé des champs d’une portlet

Lors de la réalisation d’une portlet, en cas de doute, il est possible de visualiser les valeurs par défaut généré, via la page ShowPortalPortlet dans Adm. Sys

Les attributs génériques possible
  • collapsible : si vide c’est true non opérationnel : true pour le moment

  • saveCollapsed : si vide c’est true non opérationnel : true pour le moment

  • initiallyCollapsed : si vide c’est false

Liens complémentaires
  • Voir tous les Types de portlet et les champs

  • Voir la liste complète des champs et leur description

Portlet de type Empty
type de portlet Empty

Ce décorateur permet de définir une portlet comme un conteneur d’autres portlets. Ainsi, au moyen d’une navigation par menu, on peut charger une portlet dans cette zone de contenu. Voir la portlet ExampleDetail et la portlet de menu qui la gère.

Usage classique : sert à définir une zone chargement de portlets commandée par une autre portlet contenant un menu.

Liens complémentaires

Voir tous les Types de portlet et les champs

Voir la liste complète des champs d’une portlet

Utilisation du type de portlet Decorator
type de portlet Decorator

Contient juste, le decorator qui permet la gestion des traductions dans le composant où la portlet sera utilisée (chargement des uiLabel). Ce decorator est utilisé par les portlets type Screenlet et ScreenletList.

L’usage de ce type de portlet permet de faire une portlet à partir d’un screen, sans avoir à se soucier du chargement des uiLabels, il est conseillé de l’utiliser pour être homogène en terme d’usage des uiLabel pour toutes les portlets.

Il sera utilisé chaque fois que l’on a besoin d’un screen spécifique

Champs optionnels de l’entité PortalPortlet utilisés
  • uiLabelLocation

  • useScreen, mettre Y

  • screenName

  • screenLocation

Voir la liste complète des champs d’une portlet

Attributs d’une portlet

Les portlet utilisent des attributs pour permettre de rendre configurable cette portlet.

En effet, plus une porlet est configurable, plus il sera aisée de l’utiliser dans un autre contexte, sur une autre page.

Gestion des attributs

Un exemple d’attributs standard fixé lors de la configuration : l’attribut "initiallyCollapsed" est un attribut générique utilisé par les types de portlets Screenlet et ScreenletList pour déterminer si la screenlet sera initialement fermée ou ouverte. Ici, on configure sa valeur pour une page portail et une portlet données.

<PortletAttribute portalPageId="ExampleNew" portalPortletId="FindExample2" attrName="initiallyCollapsed" attrValue="true" portletSeqId="00001"/>

Ensuite, on peut créer son propre attribut qui sera utilisé par le script (par exemple) pour rechercher une liste (par exemple une liste d’acteurs avec un rôle défini). Ainsi, on pourra utiliser la portlet pour différentes valeurs de cet attributs (par exemple, une portlet acteur permet de filtrer sur des propects, des employés,…​).

<PortletAttribute portalPageId="ExampleMgmt" portalPortletId="ListExample2" attrName="roleTypeId" attrValue="VENDOR" portletSeqId="00001"/>
Utilisation des pages portail

Une page portail est composée d’une ou plusieurs portlets organisées autour de colonnes et d’un séquençage

Et voici un exemple de configuration de page portail :

        <PortalPage portalPageId="ExampleMgmt"  sequenceNum="200" parentPortalPageId="EXAMPLE"
                           portalPageName="Example management" description="Search, list, navigation menu, edit area" ownerUserLoginId="_NA_"/>
        <PortalPageColumn portalPageId="ExampleMgmt" columnSeqId="00001" columnWidthPercentage="25"/>
        <PortalPagePortlet portalPageId="ExampleMgmt" portalPortletId="FindExample" portletSeqId="00001" columnSeqId="00001" sequenceNum="1"/>
        <PortalPagePortlet portalPageId="ExampleMgmt" portalPortletId="ExampleDetailsMenu" portletSeqId="00001" columnSeqId="00001" sequenceNum="10"/>
        <PortalPageColumn portalPageId="ExampleMgmt" columnSeqId="00002"/>
        <PortalPagePortlet portalPageId="ExampleMgmt" portalPortletId="ListExample" portletSeqId="00001" columnSeqId="00002" sequenceNum="20"/>
        <PortalPagePortlet portalPageId="ExampleMgmt" portalPortletId="ExampleDetail" portletSeqId="00001" columnSeqId="00002" sequenceNum="30"/>

Pour garantir la qualité de cette page portail, il est important de penser aux traductions (CommonPortalEntityLabels.xml) :

        <property key="PortalPage.portalPageName.ExampleMgmt">
            <value xml:lang="en">Examples management </value>
            <value xml:lang="fr">Gestion des exemples </value>
        </property>
Les Pages Portails Types

La notion de page portail type n’existe que au niveau des bonnes pratiques. Vous pouvez agencer le contenu de vos pages portails comme vous le souhaitez, mais nous avons déterminé 3 pages type que l’on rencontre dans la plupart des composants. Lors de la création de page portail pour un nouveau composant, il est conseillé de commencer par ces trois pages type

Pour avoir la liste complète, passer par l’index.

Page portail de recherche et gestion d’une entité métier majeur

Ce type de page permet de faire des recherches sur une entité métier, et à partir de la liste des résultats de visualiser chacune des entités associées. A partir de la liste des résultats, il y a aussi un lien vers une page de synthése de l’entité, c’est à dire visualiser l’ensemble des entités associées, pour une instance de l’entité.

Page portail type : Gestion
Les autres Page Type
Page portail permettant de créer pour un enregistrement d’une entité métier majeur

Ce type de page permet de créer un enregistrement d’une entité métier et puis de le compléter via l’ajout d’association à un ensemble d’entités associées.

Page Portail Type : Création
Page portail récapitulative pour un enregistrement d’une entité métier majeur

Ce type de page permet de gérer une entité métier et un ensemble (ou toutes) d’entités associées.

Page Portail Type : Recapitulative
Gestion de la sécurité

La gestion des droits d’affichage des pages portail et des portlets permet de définir ce qui est voulu en agissant à quatre niveaux :

  • Au niveau de la page portail il est possible d’associé un "groupe de sécurité", seul les personnes associés à ce groupe pourront afficher cette page et la voir dans les menus (en particulier quand la page est une page fille parentPortalPageId non vide)

  • Au niveau de chaque portlet il est possible de définir la "Security Permission" et la "Security Main Action", donc seul les utilisateurs qui font partis d’un "groupe de sécurité" qui inclus cette "Secuirty Permission Action" pourront l’afficher

  • Au niveau de page portail et portlet, en effet les autorisations d’usage d’une portlet peuvent variées selon la page portail utilisé (ex: dans une page client ou un page fournisseur, il y aura de nombreuse portlet similaire mais un commercial n’aura pas les même droit dans les deux pages).

    Il est possible de définir un service d’autorisation (il doit implémenter l’interface portalSecurityPermission) au niveau d’une page portail. Ce service est appelé lors de l’affichage d’une page portail et à chaque fois qu’une portlet est affichée (ou rafraichie) dans la page portail. Le retour de ce service contiendra forcement une variable hasPermission qui doit être à true pour pouvoir afficher la page et le résultat de ce service est mis dans le context et est disponible pour les portlet de la page (il contiendra une variable widgetPermission définie par l’interface).

    Au niveau du minilangue il y a un tag "check-widget-permission" qui permet de vérifier et/ou créer un élément de la map widgetPermission. En effet, cette instruction vérifie la présence de ${widget-permission} en tant que clé dans la map si elle n’est pas présente elle l’initialise en testant si l’utilisateur posséde la ${permission}_${action}

  • Au niveau de chaque portlet il est possible de définir des attribues (aussi appelé paramétres) qui permettre de modifier le comportement de la portlet.

    Warning
    Attention

    Si des paramétres ont une importance dans la gestion des autorisations, il est impératif que le formulaire d’édition de ces paramètres utilise aussi les règles de sécurité pour l’affichage ou non de ce paramétre.

Il y a un exemple d’usage du service d’autorisation pour une page portail dans le composant example.

Règle de nommage

Afin de faciliter la compréhension des noms de page portail et noms de portlet, des conventions de nommage ont été établies :

  • Page portail :

    • xxxxRecap : elle contient un ensemble de portlets pour visualiser et/ou gérer un objet et ses entités associées (les pages profil d’acteur ou Résumé Projet sont de ce type), plus de détails

    • xxxxMgmt : elle permet de faire une recherche, d’obtenir une liste puis aprés sélection d’un élément de visualiser/gérer entités associées par entités associées

    • sub…​ : c’est une page qui est utilisée en tant que partie d’écran

  • Portlet :

    • Findxxxs : formulaire de recherche

    • Listxxxs : portlet listant l’enregistrement de l’entité, souvent en tant que résultat de la portlet Findxxxs

    • xxxSummary : portlet de petite ou moyenne taille uniquement en affichage, destinée à être dans une colonne (25% ou 33%) pour rappeler l’objet sur lequel on travaille

    • xxxDetailsMenu : contenu identique à xxxSummary avec un menu permettant d’afficher dans un autre emplacement de l’écran le détail d’entités associés

    • xxxDetail : portlet vide permettant de définir l’emplacement (le container) où s’affiche le détail d’entités associés (cf xxxMenuDetail)

    • xxxOverview : portlet de moyenne à grande taille, uniquement en affichage, destinée à être dans la colonne principale (50% et plus) qui présente une vue générale de l’objet, au besoin à partir d’éléments d’entités associées (synthèse Projet, Commande)

    • xxxCreate : portlet de création, peut contenir des champs liés à des entités associées afin de permettre de créer l’objet et les entités associées en une seule opération. Il peut y avoir similitude de formulaire HTML (form) avec xxxOverview

    • xxxUpdate : portlet de mise à jour, ne contient que des champs liés à l’objet, la mise à jour des entités associés se fait via les portlets adéquates

    • xxxyyyys ou xxxyyyysList : xxx c’est le nom de l’entité majeure (l’objet) yyyy le nom de l’entité associée, le s pour signaler que c’est une liste

Migration d’écran standard

Pour la migration d’écran standard ofbiz en portlet, l’objectif est d’utiliser au maximum :

  1. les éléments du standard ofbiz

  2. Des portlet avec le minimum de champ définis donc avec l’usage du maximum de champ avec leur valeur par défaut.

Règles à suivre :
  1. en cas de doute, regarder comment sont paramétrés les portlet dans l’exemple (/ofbiz/plugins/example/data/ExamplePortletData.xml), cela correspond aux bonnes pratiques (une bonne pratique qui n’est pas dans exemple n’est pas une bonne pratique)

  2. sauf cas très particulier, le nom et la location des fichiers (form, menu, script, screen) doivent utiliser les valeurs par défaut

  3. sauf cas très particulier, une portlet doit avoir un type (Screenlet, ScreenletList, Decorator ou Empty)

  4. les champs composant, sous-composant et webapp doivent être correctement renseignés (webapp vide ou non)

  5. mettre dans le bon composant la portlet, une portlet issue d’une form de projectmgr est un élément de WorkEffort s’il n’y a pas de spécificité project. Les uiLabels sont surchargeables par composant donc un titre spécifique n’est pas une bonne raison; un ou des drop-downs avec des contraintes (parentType = xxx, xxxType= , …​) peuvent facilement être rendus paramétrables.

  6. sauf cas très particulier, le titre de la screenlet doit utiliser un label existant, bien faire une recherche avant d’en créer un, et si création, le mettre dans le bon composant

  7. dans la mesure du possible, utiliser uniquement une form. Si possible, celle-ci hérite du formulaire HTML (form) standard, les champs de type lien (edit, remove) étant surchargés pour être des show-portlets, et un on-event-update-area est ajouté s’il y a un bouton submit

  8. les target pour l’update ou le create doivent utiliser un retour json, afin d’avoir un affichage des messages d’erreur ou de retour correct.

  9. les actions d’édition (dans la zone d’editArea) sont la plupart de temp des uri, pas des portlets (sauf usage en tant que tel par ailleurs); par souci d’homogénéité, il faut mettre screen, form dans les mêmes locations que les portlet les utilisant. De même, si possible faire un héritage de la form standard.

  10. pour l’instant, il n’y a pas consensus concernant le lieu du bouton "annuler" mais comme qui peut le plus, peut le moins, il faut mettre un bouton annuler dans les formulaires d’édition et un bouton dans le menu (en dernier à droite) qui fait un refresh de la portlet

  11. les uri json (et les uri d’action d’édition) sont à mettre dans le portlet-controller en respectant les bonnes pratiques OFBiz (request sans appel de service sur une ligne …​)

  12. dans la mesure du possible, utiliser un script (mini-lang) plutôt qu’un screen, si le script est une copie du pavé action du screen, mettre un commentaire ; le script n’a d’intérêt que s’il est utilisé plusieurs fois ou si le résultat est utilisé dans l’en-tête du screenlet (menu ou titre ), sinon autant le mettre dans le pavé action de la form.

  13. toute partie de code dupliqué (form, screen, menu, script, java) doit posséder un commentaire pour signaler l’origine de la copie et la justification de la copie (pensez à celui qui voudra supprimer la duplication)

  14. les addons nommé portlet-nomDunComposantStdOfbiz ont pour objectif d’être reversé à OFBiz, ils ne doivent donc contenir que des éléments standards

  15. portletWidget a pour objectif d’être reversé à OFBiz, pas genericPortlet ; il faut donc dans les addons portlet-nomDunComposantStdOfbiz migrer les portlets qui n’utiliseraient pas portletWidget

  16. pour tous les liens hyperlink vers d’autres pages (par exemple d’un autre composant), si possible migrer la target vers un showPortalPage et mettre en paramétrage de la nom de la portalPage, normalement ça doit être une page de type xxxxRecap. Bien entendu, paramétrer ou faire la form d’editAttribute.

  17. pour toutes les form d’editAttribute de portlet, le nom du champ ou son titre doivent être explicites, et si possible avoir un tooltips ; tout paramètre de portlet doit avoir une valeur par défaut (la portlet doit être utilisable sans avoir besoin de saisir une valeur)

  18. Il faut au minimum disposer des deux pages portails classiques ( xxxMgmt et xxxRecap), les mettre à jour à chaque ajout de portlet

  19. Dans la mesure du possible, il faut mettre des icônes à la place de libellé. Pour cela, il faut :

    • chercher la bonne icône dans la page "Liste des icônes", et donc trouver son nom logique

    • exprimer l’image-location en utilisant la map iconsPurpose

    • si le Tooltips par défaut (celui visible dans la page Liste des portlet) est suffisamment clair pour votre usage, utilisez-le via les labels IconsTooltips_, sinon personnalisez correctement l’image-title en rédigeant un nouveau Tooltips mieux adapté

9.2.4. Catégorie EXAMPLE, ancienne

Cette catégorie de portlet regroupe les portlets du composant Example, qui pouvait servir d’exemple avant que les portlets avec "Type de portlet" apparaissent.

Example 1

EXAMPLE_1 : Example portlet n.1

Portlet très simple pour montrer :

  • l’association entre un screen et une portlet

  • l’usage de paramètres de portlet.

Les paramètre de la Portlet
  • ItemsToShow, non vide choisir une valeur parmi 5,10,15,20,25,30,35. Uniquement à titre de démonstration de paramètre à valeur multiple. ce paramètre n’est pas utilisé dans la portlet

  • SpecificTitle, C’est le libéllé qui apparait dans la portlet s’il est saisi.

Règles de Securité
  • aucune, (celles du composant)

Remarques Techniques

Portlet de OFBiz de base, pas d’usage de "Type de portlet" et sans usage de show-portlet (ajax)

Example 2

EXAMPLE_2 : Example portlet n.2

Portlet très simple pour montrer :

  • l’association entre un screen et une portlet

  • l’usage d’un paramètre de portlet pour influencer un lien.

Les paramètre de la Portlet
  • nextPortalPageId : si ce paramètre est non vide alors la portlet afficher un lien vers cette page portail (avec comme paramètre complémentaire le paramètre suivant nextParentPortalPageId)

  • nextParentPortalPageId : cf ci-dessus

Règles de Securité
  • aucune, (celles du composant)

Remarques Techniques

Portlet de OFBiz de base, pas d’usage de "Type de portlet" et sans usage de show-portlet (ajax)

Example 3

EXAMPLE_3 : Example portlet n.3

Portlet très simple pour montrer :

  • l’association entre un screen et une portlet

  • l’usage de paramètre très basique.

Les paramètre de la Portlet
  • SpecificTitle : s’affiche dans la portlet

  • SpecificSubTitle : s’affiche dans la portlet

Règles de Securité
  • aucune, (celles du composant)

Remarques Techniques

Portlet de OFBiz de base, pas d’usage de "Type de portlet" et sans usage de show-portlet (ajax)

9.2.5. Catégorie EXAMPLE_PORTLET_TYPE, exemple à suivre

Cette catégorie de portlet regroupe toutes les portlets du composant Example, qui peuvent servir d’exemple.

Liste des icônes utilisables dans les portlets

IconsList : Liste des icônes utilisables dans les portlets avec iconsPurpose et IconsTooltips_

Permet d’afficher toutes les icones référencées dans iconsPurpose, c’est un outil pour le développeur pour trouver la bonne icone en fonction de son. besoin

Les paramètre de la Portlet
  • aucun

Règles de Securité
  • aucune, (celles du composant)

Rechercher un exemple

FindExample : portlet (type Screenlet) de critères de recherche pour la liste des exemples

Portlet de recherche sur une entité, le resultat est affiché dans une autre portlet nomé ListExample

Les paramètre de la Portlet
  • aucun

Règles de Securité
  • aucune, (celles du composant)

Liste des exemples

ListExample : portlet (type ScreenletList) listant des exemples selon les critères de recherche fournis par une portlet de recherche (ici FindExample)

Portlet de liste sur une entité, cette liste est filtré en fonction des critère de recherche indiqué dans la portlet FindExample. Cette portlet de liste contient 2 liens, l’un vers une autre portlet dans la même page et l’autre vers une autre page portail.

Le lien vers l’affichage de la portlet de résumé, utilise l’option du show-portlet qui permet de refermer la portlet de liste quand il est clické. L’utilisateur peut la ré-ouvrir sans relancer la recherche.

Les paramètre de la Portlet

usage de GenericRecapPageParam pour le formulaire d’édition de paramètre

  • recapPageId : correspond à l’identifiant de la page portail qui sera ouverte via le lien de l’icone "Page récapitulative" pour chaque item de la liste.

Règles de Securité
  • aucune, (celles du composant)

Résumé et menus associés pour un exemple (DetailsMenu)

ExampleDetailsMenu : portlet(type Screenlet) de résumé et menus liés à un exemple, et utilisant une portlet de contener de détails (ici ExampleDetail)

Portlet d’affichage du résumé d’un exemple (un objet métier) avec un menu permettant d’afficher l’ensemble des détails de l’exemple en question. Chacune des options du menu affiche la portlet correspondante, toujours dans le même lieu de la page portail.

Cette portlet utilise l’attribue pkIdName, donc elle ne s’affiche pas si elle ne reçoit pas un exempleId. C’est pour ça qu’elle n’apparait que suite au click sur un lien de la liste des exemples

Les paramètre de la Portlet
  • aucun

Règles de Securité
  • aucune, (celles du composant)

Contener utilisé pour afficher des portlets de détails pour un exemple

ExampleDetail : portlet (type Empty) servant de contener aux portlets de détails d’un exemple (commandé par le menu de la portlet ExampleDetailsMenu)

Portlet ultra simple, son utilité est juste de définir où s’afficheront les portlet de detail quand on click sur une option du menu de la portlet ExampleDetailMenu.

Les paramètre de la Portlet
  • aucun

Règles de Securité
  • aucune, (celles du composant)

Montrer un exemple (tous les champs)

ShowExample : portlet (type Screenlet) de visualisation/modification d’une entité majeure (ici, exemple)

Affichage de la synthèse d’un exemple (un objet métier), l’ojectif est de montrer l’ensemble des informations importantes lié à un exemple, la plupart des champs de exemple et pourquoi pas certain élément des entités lié.

En mode édition, seul les champs de l’entité exemple seront modifiable.

Les paramètre de la Portlet
  • aucun

Règles de Securité
  • aucune, (celles du composant)

Créer un exemple

CreateExample : portlet (type Screenlet) de création d’un exemple et ses entités secondaires

Portlet permettant la création d’un exemple.

Les paramètre de la Portlet
  • aucun

Règles de Securité
  • aucune, (celles du composant)

Listes des lignes d’exemple

ExampleItems : portlet (type ScreenletList) de gestion des lignes d’un exemple (affiche la liste et le menu)

Portlet permettant l’affichage et la gestion -création, modification, suppression- des lignes d’un exemple.

Cette portlet utilise l’attribue pkIdName="exampleId", donc elle ne s’affiche pas si elle ne reçoit pas un exampleId. C’est pour ça qu’elle n’apparait que suite à la sélection d’un exemple, dans la page portail récapitulative d’un exemple.

Les paramètre de la Portlet

usage de GenericEditEditOrShowParam pour le formulaire d’édition de paramètre

  • showEditButton : Y ou N, affiche ou non le boutton d’édition d’une ligne d’exemple.

  • showScreenletMenu : Y ou N, affiche ou non le menu d’édition dand le champ titre de la portlet.

Règles de Securité
  • aucune, (celles du composant)

Liste des statuts de l’exemple

ExampleStatus : Liste tous les (évolution de) statuts de l’exemple (portlet utilisant un modéle de portlet et showPortlet)

Portlet permettant l’affichage et la gestion -création, modification, suppression- de l’historique de changement de statut de l’exemple.

Cette portlet utilise l’attribue pkIdName="exampleId", donc elle ne s’affiche pas si elle ne reçoit pas un exampleId. C’est pour ça qu’elle n’apparait que suite à la sélection d’un exemple, dans la page portail récapitulative d’un exemple.

Les paramètre de la Portlet

usage de GenericEditEditOrShowParam pour le formulaire d’édition de paramètre

  • showEditButton : Y ou N, affiche ou non le boutton d’édition d’une ligne d’exemple.

  • showScreenletMenu : Y ou N, affiche ou non le menu d’édition dand le champ titre de la portlet.

Règles de Securité
  • aucune, (celles du composant)

Association de caractéristiques à un exemple

ExampleFeatureAppls : portlet listant les associations de caractéristiques d’exemple (type Screenlet, bien qu’utilisant une liste, pour pouvoir ajouter un menu externe)

Portlet permettant l’affichage et la gestion des liens -création, modification, suppression- entre l’exemple et un ensemble de caractèristique d’exemple.

Cette portlet utilise l’attribue pkIdName="exampleId", donc elle ne s’affiche pas si elle ne reçoit pas un exampleId. C’est pour ça qu’elle n’apparait que suite à la sélection d’un exemple, dans la page portail récapitulative d’un exemple.

Les paramètre de la Portlet

usage de GenericEditEditOrShowParam pour le formulaire d’édition de paramètre

  • showEditButton : Y ou N, affiche ou non le boutton d’édition d’une ligne d’exemple.

  • showScreenletMenu : Y ou N, affiche ou non le menu d’édition dand le champ titre de la portlet.

Règles de Securité
  • aucune, (celles du composant)

Rechercher des caractéristiques d’exemple

FindExampleFeature : portlet (type Screenlet) de critères de recherche pour la liste des caractéristiques d’exemple

Portlet de recherche sur l’entité caractèristique d’exemple.

Les paramètre de la Portlet
  • aucun

Règles de Securité
  • aucune, (celles du composant)

Liste les caractéristiques d’exemple

ListExampleFeature : portlet (type ScreenletList) listant des caractéristique d’exemples selon les critères de recherche fournis par une portlet de recherche (ici FindExampleFeature)

Portlet de Liste sur l’entité caractèristique d’exemple.

Les paramètre de la Portlet
  • aucun

Règles de Securité
  • aucune, (celles du composant)

9.2.6. Catégorie Example

Cette catégorie de portlet regroupe toutes les portlets du composant Example, qui peuvent servir d’exemple.

portletName

portalPortletId : portletdescription

image::portlets/xxxxxxxx.png[]

xxxxxxxxxxxxxxxxx

à compléter

Les paramètre de la Portlet
  • xxxxxxxxxxxx

  • aucun

Limitations ou futures fonctionnalitées
  • xxxxxxxxxxxxxxxxx

Règles de Securité
  • xxxxxxxxxxx

  • aucune, (celles du composant)

  • utilise le service portalPermissionIsCustomer avec VIEW

Remarques Techniques

xxxxxxxxxxxxxxxxxxx

Portlet de OFBiz de base, pas d’usage de "Type de portlet" et sans usage de show-portlet (ajax)

9.2.7. Liste des icônes utilisable dans OFBiz

Les icônes sont utiles pour rendre l’interface utilisateur plus agréable.

Afin de garantir l’homogénéité dans l’application, un nom logique a été donné à chacune des icônes et il est conseillé de n’utiliser que celle-ci.

Afin de pourvoir changer de jeux d’icône en fonction du thèmes, il est conseillé de ne jamais accéder en direct au fichier image mais de passer par les fichiers properties adéquate.

Visualiser les listes des Icons disponible

Dans le composant exemple il y a une page qui vous permet de visualiser l’ensemble des icônes disponibles et associés à un nom logique

Fil d’ariane - Applications > Application exemple > Liste des icônes utilisable dans les portlets

Dans cette page il est indiqué en première colonne : le nom logique de l’icône (dans l’exemple ci-dessus Details).

En seconde colonne il est indiqué

  • soit ce qui sera affiché en tant qu' "image-title" donc lorsque la souris sera sur l’icône,

  • soit la bonne pratique à utiliser.

Si vos laissez la souris sur l’image de l’icône vous aurez l' [image-title] par défaut.

Liste des icons partie1
Liste des icons partie1
Liste des icons partie1

Voici un exemple d’usage de icône Details

<show-portlet portlet-id="ShowExample" image-location="${iconsPurpose.Details}"
                                         image-title="${uiLabelMap.IconsTooltips_Details}">

Dans cette page il est indiqué en première colonne : le nom logique de l’icône (dans l’exemple ci-dessus Details).

En seconde colonne il est indiqué soit ce qui sera affiché en tant qu' "image-title" donc lorsque la souris sera sur l’icône, soit la bonne pratique à utilisé.

Si vos laissez la souris sur l’image de l’icône vous aurez l' "image-title" par défaut.

9.3. Packager ses développements en addon

January 24, 2013
en cours de revu en juin 2018

L’addon manager permet de créer un addon avec l’ensemble des fichiers modifiés ou créer dans le cadre du développement d’une fonctionnalité.

9.3.1. Les différents format de fichier patch

Depuis la création de l'adm, nous cherchons à avoir un format pour les fichier patch qui soit le plus pratique et le plus résistant aux modifications du fichier source par ailleurs.

pop

Les fichiers avec pop dans l’extention du nom de fichier (xxxx.9.pop.patch) utilise ce format, c’est le format standard des patch unix. C’est le format par défaut de l’adm, quand vous ajoutez un fichier dans un addon avec la commande "adm add-file" .

exemple de contenu d’un fichier patch de type pop:

@@ -110,6 +110,8 @@
     <entity-data-reader name="ext"/>
     <entity-data-reader name="ext-test"/>
     <entity-data-reader name="ext-demo"/>
+    <entity-data-reader name="asso"/><!-- #Eam# : association -->
+    <entity-data-reader name="asso-demo"/><!-- #Eam# : association -->

     <field-type name="hsql" loader="fieldfile" location="fieldtypehsql.xml"/>
     <field-type name="derby" loader="fieldfile" location="fieldtypederby.xml"/>
dop

Les fichiers avec dop dans l’extention du nom de fichier (xxxx.9.dop.patch) utilise ce format, c’est un format sémantique, il est actuellement utilisé pour la plupart des fichiers xml d’ofbiz.

L’objectif de ce format est de permettre les ajouts ou suppressions d’un ou plusieurs noeuds au niveau de noeud (balise) existant en utilisant un (ou 2) attribue en tant qu’identifiant pour déterminer où ajouter (ou supprimer). De plus les changement doivent êtres appliqué dans le fichier sans que l’ensemble du formattage du fichier soit modifié. L’attribues utilisé dépend du type de fichier xml (menu, form, screen, properties, …​).

Vous devez utiliser l’option -d pour générer le patch au format dop lors de la commande add-file de l’adm.

Note
Si la génération du fichier au format dop échoue, c’est le format pop qui est utilisé
C’est très simple de créer manuellement des fichiers au format dop

exemple 1 de contenu d’un fichier patch de type dop:

<?xml version="1.0" encoding="UTF-8"?>
<patch>
<x:add path="/" previous="/property[CommonBy]/">
<!--#Bam# addonAjoutCategory -->
    <property key="CommonCategory">
        <value xml:lang="en">Category</value>
        <value xml:lang="es">Categoría</value>
        <value xml:lang="fr">Catégorie</value>
    </property>
<!--#Eam# addonAjoutCategory -->
</x:add>
</patch>

exemple 2 de contenu d’un fichier patch de type dop:

<?xml version="1.0" encoding="UTF-8"?>
<patch>
<x:add path="/menu[PartyAppBar]/" previous="/menu[PartyAppBar]/menu-item[find]/">
<!--#Bam# portlet-party -->
        <menu-item name="portalPage" parent-portal-page-value="PartyMgmt"/>
<!--#Eam# portlet-party -->
</x:add>
</patch>
xpp

Les fichiers avec xpp dans l’extention du nom de fichier (xxxx.9.xpp.patch) utilise ce format, c’est un format patch xml utilisant une syntaxe xpath standard, c’est actuellement utilisé pour les patch sur les fichiers docbook.

Ce format est encore dans une phase expérimentale, nous étudions les possibilités de le généraliser. L’application de ce type de patch est faite avec le parser xml standard utilisé par ofbiz, donc le formattage du fichier n’est pas conservé après application d’un patch.

Note
Actuellement il n’est pas possible de générer de fichier à ce format à partir de l’adm, il faut écrire le fichier manuellement.

exemple de contenu d’un fichier patch de type xpp:

<patch>
<add path="//:section[@xml:id='about']/:para[2]" position="after">
    <!--#Bam# test-xpath -->
    <para>This is added by the test addon for xpath based patch fro xml files</para>
    <!--#Eam# test-xpath -->
</add>
<add path="//:section[@xml:id='about']/:sect1[2]/:sect2[2]/:itemizedlist[1]/:listitem[2]/:para" position="before">
    <!--#Bam# test-xpath -->
    <para>Manage recruitment (duplicated by the test addon)</para>
    <!--#Eam# test-xpath -->
</add>
<add path="//:section[@xml:id='HUMANRES_EditEmployeeTrainings']/:itemizedlist[2]" position="last">
    <!--#Bam# test-xpath -->
    <listitem>
        <para>this para and its parent are duplicated by the test addon</para>
    </listitem>
    <!--#Eam# test-xpath -->
</add>
</patch>

9.3.2. Comment et où rédiger l’aide d’un addon

L’aide de l’addon est obligatoire quand on veux suivre le processus qualité (passage dans ofbizextra-incubator).

L’aide de l’addon est à destination des personnes avec une expérience OFBiz pour leur permettre de déterminer si l’addon correspond à leur besoin ou non (sur le plan technique, fonctionnel, niveau de qualité, ..). Il peut contenir des liens vers l’aide utilisateur si elle existe mais il n’a pas pour objectif d’être l’aide utilisateur final.

Conventions et formalismes

L’aide doit être rédigé au format docbook et doit correspondre à une section (au sens docbook) car ce contenu sera inclus dans le chapitre des aides des addons installés. Donc le fichier doit débuter (après la license) par

<section xml:id="ADDON_addonName"
     xmlns="http://docbook.org/ns/docbook"
     xmlns:xl="http://www.w3.org/1999/xlink"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://docbook.org/ns/docbook">

avec addonName remplacé par le nom de l’addon, l’orthographe doit être exact.

Le nom du fichier doit être HELP_ADDON_addonName.xml avec addonName remplacé par le nom de l’addon, l’orthographe doit être exact. Il est possible de le suffixer avec le code langue (ex: HELP_ADDON_addonName_FR.xml). Ce fichier doit se trouver dans un répertoire helpdata à la racine de l’addon.

Warning
La convention de nomage est importante
C’est cette convention qui est utilisé par l’adm pour copier au bon emplacement le fichier lors de l’installation de l’addon
Contenu

Il n’y a rien d’obligatoire mais le plus courament on utilise un certain nombre de sous-section (de simplesect) avec les titres suivants

    <simplesect>
      <title>Business coverage</title>
      <!-- or User requirements at the development beginning -->
      <para> </para>
    </simplesect>

    <simplesect>
      <title>Technical architecture</title>
      <!-- Functional choice or parameter, UFO diagram associated,
           entity or service to use, menu or screen to define -->
      <para> </para>
    </simplesect>

    <simplesect>
      <title>Version status</title>
      <!-- to explain if this addon is usable for what
           currently is it a alpha version, a beta version, or a usable one

           this simplesect can be remove when addon is published with finish status
      -->
        <itemizedlist>
            <listitem ><para>Version 0.X , work in progress, so not really usable, but show which will exist</para>
            </listitem>
        </itemizedlist>
    </simplesect>

    <simplesect>
      <title>Install process :</title>
      <!-- is it necessary to compile, generate helpn, load datat, ....-->
        <orderedlist>
            <listitem ><para> </para></listitem>
        </orderedlist>
    </simplesect>

    <simplesect>
      <title>Parameters :</title>
        <para>currenlty nothing</para>
        <itemizedlist>
            <listitem ><para>XXXX : with this parameters you can ..</para></listitem>
        </itemizedlist>
    </simplesect>

    <simplesect>
      <title>How-to test :</title>
        <para>No automatics testing process exist, user can test after follow install process</para>
        <!--
        <orderedlist>
            <listitem ><para>follow install process</para></listitem>
            <listitem ><para>do xxxx</para></listitem>
            <listitem ><para>run selenium test</para></listitem>
            <listitem ><para>result should be ....</para></listitem>
        </orderedlist>
        -->
    </simplesect>

    <simplesect>
      <title>Dependency :</title>
        <orderedlist>
            <listitem ><para>Mandatory, portlet widget, because it use portlet ;-) </para></listitem>
        </orderedlist>
    </simplesect>
Fichiers patch à ajouter

Actuellement l’inclusion des fichiers d’aide d’addon dans le chapitre des addons installés n’est pas automatique, il faut le faire via des fichiers patch :

  1. inclusion dans le chapitre en anglais, ajouter un fichier OFBIZ_ADDONS.xml.0.xpp.patch dans framework/webtools/data/helpdata/docbookhelp

    <?xml version="1.0" encoding="UTF-8"?>
    <patch xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://docbook.org/ns/docbook"
           xmlns:xi="http://www.w3.org/2001/XInclude"
           xmlns:xlink="http://www.w3.org/1999/xlink"
           xmlns:xml="http://www.w3.org/XML/1998/namespace" >
    <add path="//:section[@xml:id='INSTALLED_ADDONS']" position="last">
        <xi:include href="../../../../../.addons/helpdata/HELP_ADDON_addonName.xml" />
    </add>
    </patch>

    avec addonName remplacé par le nom de l’addon, l’orthographe doit être exact.

  2. Idem dans le chapitre en français OFBIZ_ADDONS_FR.xml.0.xpp.patch (même si votre fichier d’aide est en anglais)

  3. ajout des informations nécessaire pour avoir le lien entre l’addon et l’aide de l’addon dans l’adm-gui. ajouter un fichier WebtoolsHelpData.xml.0.dop.patch dans framework/webtools/data/helpdata/

    <?xml version="1.0" encoding="UTF-8"?>
    <patch>
    <x:add path="/" previous="//">
      <WebhelpTarget defaultTargetId="ADDON_addonName" helpTopic="ADDON_addonName" /><!--#Eam# addonName  -->
    </x:add>
    </patch>

    avec addonName remplacé par le nom de l’addon, l’orthographe doit être exact.

9.4. La gestion de l’aide utilisateur dans OFBiz

January 24, 2013
en cours de revu à partir de juin 2018

La documentation de Apache OFBiz® est en cours de refonte complète et ce qui suit concerne les anciennes manière de gestion de la documentation.

Pour plus de détail sur les règles et outils actuels il vaut mieux lire documentation_guidelines

Actuellement il y a 2 systèmes de gestion de l’aide utilisateur, celui qui est inclus dans le noyau OFBiz et celui que vous êtes entrain d’utiliser ;-) qui est ajouté par l’addon webhelp. C’est au niveau du composant qu’est défini le système d’aide qui est utilisé par défaut. Les deux moteurs utilise le format docbook:: pour la rédaction des fichiers d’aide.

Le moteur d’aide qui est inclus dans le noyau OFBiz utilise le moteur de gestion de contenu de OFBiz et construit l’aide de manière dynamique, mais pour des raisons de performance, l’interpréteur docbook utilisé dans ce cadre ne traite qu’un nombre réduit de type de balise docbook. Ce moteur d’aide est pratique lorsque l’aide doit pouvoir être modifiable par les utilisateurs. Dans la suite de l’aide nous le nommerons ofbizhelp::.

Le moteur d’aide fournit par l’addon webhelp est issu du projet Webhelp du projet docbook. Il permet de générer un site d’aide (en html) à partir d’un ou de plusieurs fichiers au format docbook::. Le nombre de type de balise docbook supporté est donc très étendu.

Ce chapitre de l’aide du composant Exemple doit vous permettre de comprendre comment rédiger et faire fonctionner l’aide à l’intérieur de OFBiz, avec l’un ou l’autre des moteurs ofbizhelp:: ou webhelp::. Il détaille aussi les base du format docbook:: et donne des exemples d’usage de chacune des balises DocBook utilisé couramment. Si nécessaire, ou pour plus de détail, vous pouvez regarder les fichiers sources pour l’aide du composant Exemple, selon la nature de l’information, ils se trouvent dans le répertoire ofbiz/plugins/example/data/helpdata pour ofbizHelp ou dans le sous-répertoire docbookhelp pour le webhelp.

9.4.1. Organisation des fichiers d’aide

Warning
Attention

L’Organisation des fichiers est vraiment différente entre ofbizhelp:: et webhelp::.

Dans un cas (ofbizhelp) le module gestion de contenu est utilisé, dans l’autre cas un ensemble de fichier static sont généré à partir d’un seul fichier (mais qui peut contenir des includes)

C’est au niveau du fichier ofbiz-component qu’est indiqué quel système d’aide est utilisé, pour utiliser le webhelp il faut ajouter dans la balise webapp, la propriété use-standard-help avec la valeur false.

Avec ofbizhelp
Les différents types de fichier

Comme le module de gestion de contenu est utilisé, il faut

  • d’une part écrire des fichiers

    docbook

    (qui seront chacun un élement de contenu);

  • d’autre part écrire la configuration d’usage de ces fichiers, c’est à dire :

    1. associer chaque fichier

      docbook

      avec un identifiant d’élément de contenu;

    2. décrire la relation entre les éléments de contenu;

    3. décrire les associations entre les clé de recherche utilisateur et les éléments de contenu.

Les fichiers d’aide (au format docbook::) sont contenus dans le répertoire /data/helpdata/ de chacun des composants. Ainsi pour le composant Exemple, on trouve le fichier d’aide que vous êtes en train de lire (ainsi que tous les sujets accessibles dans l’aide du composant Exemple) dans :

  /ofbiz/plugins/example/data/helpdata/HELP_EXAMPLE_helpfile.xml

La configuration de l’usage de ces fichiers se fait avec un fichier xml (nomé nomDuComposantHelpData.xml) qui se trouve dans le répertoire /data/helpdata/ de chacun des composant. On y trouve la définition des Content et DataResource correspondants aux fichiers d’aide définis dans le composant, les configurations de langues, la configuration de l’arbre de navigation de l’aide,…​

Pour le composant exemple le fichier se trouve :

  /ofbiz/plugins/example/data/ExampleHelpData.xml
Le contenu d’un fichier docbook d’aide

Chaque fichier correspond à une section d’un ensemble, il démarre donc par la balise .

Le contenu de la section suit la syntaxe docBook, celle-ci est décrit dans la section Syntaxe d’un fichier docBook.

Les fichiers docBook du composant exemple contienne au moins un exemple de chacun des éléments de syntaxe qu’il est possible d’utiliser

La configuration de l’aide

Pour chaque composant, il y a un fichier de configuration. Dans celui-ci on définit :

  • pour chaque fichier d’aide, un contenu (Content) et une ressource (DataResource). Voici un exemple de définition pour le fichier d’aide plugins/example/data/helpdata/HELP_EXAMPLE_main.xml :

    <DataResource dataResourceId="EXAMPLE_main" localeString="en" dataResourceTypeId="OFBIZ_FILE"
        objectInfo="plugins/example/data/helpdata/HELP_EXAMPLE_main.xml" dataTemplateTypeId="NONE" statusId="CTNT_IN_PROGRESS" dataResourceName="Main page" mimeTypeId="text/xml" isPublic="Y" />
    <Content contentId="EXAMPLE_main" contentTypeId="DOCUMENT" contentName="Main menu." templateDataResourceId="HELP_TEMPL" dataResourceId="EXAMPLE_main" statusId="CTNT_IN_PROGRESS" mimeTypeId="text/html"/>
Important
Bien renseigner les labels

Ils sont utilisés comme labels dans l’arbre de navigation de l’aide (principalement "contentName").

  • On configure l’arborescence de l’aide au moyen d’associations ContentAssoc de type TREE_CHILD et du sequenceNum qui permet d’ordonner les items :

    <ContentAssoc contentId="HELP_EXAMPLE" contentIdTo="EXAMPLE_main"     contentAssocTypeId="TREE_CHILD" sequenceNum="001"" mapKey="EXAMPLE_main"/>
    <ContentAssoc contentId="HELP_EXAMPLE" contentIdTo="EXAMPLE_help"     contentAssocTypeId="TREE_CHILD" sequenceNum="002" mapKey="EXAMPLE_docbook"/>
      <ContentAssoc contentId="EXAMPLE_help" contentIdTo="EXAMPLE_docbook"  contentAssocTypeId="TREE_CHILD" sequenceNum="001" mapKey="EXAMPLE_docbook"/>
      <ContentAssoc contentId="EXAMPLE_help" contentIdTo="EXAMPLE_helpfile" contentAssocTypeId="TREE_CHILD" sequenceNum="002" mapKey="EXAMPLE_helpfile"/>
    <ContentAssoc contentId="HELP_EXAMPLE" contentIdTo="EXAMPLE_devel"    contentAssocTypeId="TREE_CHILD" sequenceNum="003" mapKey="EXAMPLE_docbook"/>
    ...

    Le champ mapKey permet de faire le lien entre "NOMduCOMPOSANT_URI" et l’élément de contenu à afficher dans l’aide.

  • Pour internationaliser l’aide, on peut associer les pages pour chaque langue au moyen de ContentAssoc de type ALTERNATE_LOCALE :

    <DataResource dataResourceId="EXAMPLE_main_FR" localeString="fr" dataResourceTypeId="OFBIZ_FILE"
        objectInfo="plugins/example/data/helpdata/HELP_EXAMPLE_main_FR.xml" dataTemplateTypeId="NONE" statusId="CTNT_IN_PROGRESS" dataResourceName="Accueil" mimeTypeId="text/xml" isPublic="Y" />
    <Content contentId="EXAMPLE_main_FR" contentTypeId="DOCUMENT" contentName="Accueil" templateDataResourceId="HELP_TEMPL" localeString="fr" dataResourceId="EXAMPLE_main_FR" statusId="CTNT_IN_PROGRESS" mimeTypeId="text/html"/>
    <ContentAssoc contentId="EXAMPLE_main" contentIdTo="EXAMPLE_main_FR" contentAssocTypeId="ALTERNATE_LOCALE" fromDate="2006-01-12 01:01:01"/>
Accès et navigation dans les fichiers d’aides

Il est possible d’y accéder de plusieurs manière :

  • à un composant ou sous-composant, l’accès est possible dans l’arbre de navigation, le contenu est associé à la racine de l’aide (contentId=HELP_ROOT) avec le type enfant (contentAssocTypeId="TREE_CHILD");

  • à un écran, l’accès est possible grâce à l’icône d’aide (en haut à droite sur la plupart des thèmes), le contentId est recherché sur ContentAssoc.mapKey avec une valeur "helpTopic" batie sur le webSite du composant et l’URI utilisée pour afficher le screen ;

  • à une portlet, l’accès est possible par lien avec la page portail d’aide (qui contient cette portlet), le lien est un showHelp (comme pour un screen) avec la valeur "helpTopic" batit avec "PORTLET_" et le portletId

  • à une page portail, l’accès est possible grâce à l’icône d’aide si le portalPageId est renseigné, le contenu est lié par la page portail et est affiché en haut de la liste des portlets la contenant.

Avec webhelp

Il y a un fichier docbook par composant et par langue, ils se situent dans le répertoire data/helpdata/docbookhelp du composant en question, son nom doit être préfixé par webhelp_ et suffixé par le code langue. De la même manière, il y a un répertoire préfixé par webhelp_ et suffixé par le code langue et le mot images pour toutes les images nécessaire. Pour le composant exemple en français cela donne

  /ofbiz/plugins/example/data/helpdata/docbookhelp/webhelp_exemple_fr.xml
  /ofbiz/plugins/example/data/helpdata/docbookhelp/webhelp_exemple_fr_image/

A partir de ce fichier, un ensemble de fichier html sont généré via la commande ant webhelp à la racine de ofbiz ou à la racine d’un composant. Ce processus de transformation utilise les outils du projet docbook::, donc la quasi totalité des balises sont prise en compte

Langue et l’aide et message d’avertissement

Afin de gérer des correspondances entres langues et/ou le message d’avertissement si l’aide n’est pas disponible pour la langue demandé, il y a un fichier property WebhelpForLanguage.xml au niveau du composant commonext. Par composant, et pour chaque langue est indiqué si l’aide est disponible dans cette langue (il y a alors le code langue à utiliser) et si non (il y a warning_ en préfixe) quel langue utiliser à la place. Par exemple :

<property key="manufacturing">
    <value xml:lang="de">de</value>
    <value xml:lang="en">warning_english_en</value>
    <value xml:lang="fr">warning_anglais_en</value>
    <value xml:lang="nl">nl</value>
</property>

Pour toutes les code langues qui renvoi "de" ou "nl" l’aide sera affiché dans ces langues, pour tous les codes langue commençant par en, l’aide sera affiché en anglais. Pour tous les codes langues non définis, il y aura un message d’avertissement et c’est l’anglais qui sera affiché. Pour les codes langue qui arriverons sur le fr, il y aura un message d’avertissement et c’est l’anglais qui sera affiché.

Accès et navigation dans les fichiers d’aides

L’arbre de navigation est constitué à partir des balises structurants le document (book, chapter, section, section, section, …​, simplesect). Chaque section correspond à une page d’aide, si vous souhaitez subdiviser une page alors utilisez simplesect.

Afin de pouvoir accéder à une page d’aide à partir d’un lien d’aide, il faut lui donner un id ( xml:id ) quelque soit le niveau (chapter, section, section, section, …​, simplesect), ensuite il faire indiquer la correspondance entre des éléments fonctionnels (view_map, portalPage, portlet) et les id indiqués dans le fichier. La correspondance est réalisé via l’entité WebhelpTarget, celle ci est lu lors du click sur un des boutons d’aide.

Si aucune correspondance n’est trouvé dans la table WebhelpTarget entre les éléments fonctionnels et un id de l’aide alors un message d’avertissement sera affiché pour signaler que la page d’accueil (l’id about) sera affiché.

Il existe deux type de bouton (ou lien) d’aide, un au niveau de chaque page et un en tant que titre de chacune des portlet. Pour déterminer l’id qui sera ouvert :

  • En provenance d’une portlet (donc lien au niveau du titre de la portlet) :

    1. S’il n’y a pas d’aide pour une portlet, le titre d’une portlet peux ne pas être un lien, pour cela il suffit d’ajouter l'

      attribue

      helpAvailable pour cette portlet - page avec la valeur N

    2. L’élément fonctionnel utilisé pour une portlet (le 3iem paramètre de la fonction lookup_help, avec le 5iem paramètre à Y) est par défaut PORTLET_portalPortletId mais il est possible de mettre une autre valeur en utilisant le champ helpName de la table PortalPortlet ou en tant qu'

      attribue de la portlet
    3. La première recherche dans WebhelpTarget est faite en utilisant le portalPageId ( ou originalPortalPageId s’il est non vide) en tant que préfixe, ( c’est le 4iem paramètre de la fonction lookup_help ) : portalPageId_helpTopic

    4. Si la recherche précédente ne donne pas de résultat, le nom du composant est utilisé en tant que préfixe : component_helpTopic

    5. Si la recherche précédente ne donne pas de résultat, la recherche est réalisé avec helpTopic

  • En provenance d’une page portail (donc le bouton en entête de page et le champ portalPageId non vide, c’est le 4iem paramètre de la fonction lookup_help ):

    1. L’élément fonctionnel utilisé pour une page portail (le 4iem paramètre de la fonction lookup_help) est toujours l’identifiant de la page portail (portalPageId). Avant toutes recherche dans la table WebhelpTarget portalPageId est remplacé par originalPageId si celui-ci est non vide pour cette page portail

    2. La page portail est lu pour vérifier si le champ helpTargetId est vide ou non afin de déterminer la valeur de helpTopic : portalPageId ou helpTragetId

    3. Ensuite la recherche dans la table WebhelpTarget est réalisé avec le nom du composant en tant que préfixe : component_helpTopic

    4. Si la recherche précédente ne donne pas de résultat, la recherche est réalisé avec helpTopic

  • En provenance d’un écran (donc lien d’aide au niveau de la page) :

    1. L’élément fonctionnel utilisé pour un écran (le 3iem paramètre de la fonction lookup_help avec le 5iem élément à N) correspond à une entrée view-map du controller, c’est cette valeur qui sert de helTopic

    2. Ensuite la recherche dans la table WebhelpTarget est réalisé avec le nom du composant en tant que préfixe : component_helpTopic

    3. Si la recherche précédente ne donne pas de résultat, la recherche est réalisé avec helpTopic

Note

L’ensemble des recherches dans la table WebHelpTarget est indépendant du code langue, il est donc important que tous les fichiers docbook d’un même composant (mais différent selon la langue) possède la même liste d’ID.

Si un ID appelé par la fonction lookup_help est absent, la page d’erreur renvoyé sera une page technique et pas une page à destination des utilisateurs.

Table 2. Détail des champs de l’entité WebhelpTarget
Non du champ Usage

defaultTargetId

la clé constitué selon l’endroit où se trouve le bouton d’aide (cf ci-dessus)

helpTopic

La balise dans le webhelp vers lequel diriger. Soit juste un identifiant si c’est dans le fichier webhelp de l’application, soit un uri de la webapp ofbizhelp (ex: example_fr/content/WEBHELP_FILES.html) si le champ helpTopicIsUri == Y

warningMessage

Y ou N, par défaut à N, si Y alors le message WarningNoHelpAvailableGotoDefault, "Il n’y a pas d’aide pour ce sujet, vous allez être redirigé vers la page ${defaultTopic}", apparait à l’utilisateur. si defaultTopic n’est pas renseigné c’est about qui est utilisé .

defaultTopic

si vide c’est about qui est utilisé, pour la traduction c’est commonUilabel qui est utilisé

helpTopicIsUri

Y ou N, si vide c’est N

9.4.2. Comment écrire un document au format docbook (fichier d’aide)

Ce fichier a pour objectif de contenir un exemple de ce qu’il est possible de mettre dans un fichier docbook:: utilisé en tant qu’aide d’ofbiz. Au delà de sa lecture en tant qu’aide, il faut surtout l’ouvrir en tant que fichier xml. Pour l’affichage de l’aide avec ofbizhelp:: seules quelques balises docbook sont interprétées, cette aide est plutôt orienté webhelp, donc sans limitation.

Pour ofbizhelp:: l’interprétation de ces balises est réalisée par un fichier ftl HelpTemplate.ftl

Il est conseillé de prendre une indentation uniquement de 2 caractères, afin de limiter la largeur du fichier.

La balise para permet de gérer les paragraphes et, pour l’instant, c’est ce qui permet de gérer les retours à la ligne choisis. Le nombre d’espace ou les retours à la ligne dans le texte docbook sont ignorés, par exemple une ligne vide entre 2 para dans le fichier docbook ne génère pas d’espace entre les paragraphes. Quand il est nécessaire de forçer ce comportement, de manière exceptionnelle, il est possible d’inclure du code html. Par exemple pour générer des espaces ou des retours à la ligne, mais attention lors de la génération de pdf ou selon le type de transformation en html, ces éléments apparaîtront et ne seront pas interprétés.

<para>Petit paragraphe</para>
pour insérer une ligne blanche entre deux paragraphe <para>&amp;nbsp;</para>
<para>Petit paragraphe avec un retour &amp;lt;br \>à la ligne au milieu</para>
Qu’est ce qu’une section

Lorsque l’utilisateur demande l’affichage de l’aide (click sur l’icone) le système recherche le (ou les fichiers) associé(s) et l’affiche (pour plus de détail sur l’organisation de l’aide lire Gestion de l’aide utilisateur dans Apache OFBiz).

La balise title du fichier sera le titre de la page, et il est possible d’avoir des sous-section, autant qu’on en veut et avec autant de niveau que voulu, mais une section est habituellement une page. Dans la balise title, il est possible de mettre une balise anchor (ancre) pour pouvoir faire un lien (interne ou externe) directement vers cette section

<section xml:id="WhatIsSection">
  <title>Qu'est ce qu'une section</title>
  <para>Lorsque l'utilisateur demande l'affichage ...</para>
  <para>La balise title du fichier sera le titre de la page ....</para>
</section>
Sous-section à l’interieur d’un page

Utilisez simplesect, par contre il n’est possible d’avoir qu’un seul niveau dans une page

<simplesect>
  <title>Sous-section à l'interieur d'un page</title>
  <para>Utilisez simplesect, par contre il n'est possible d'avoir qu'un seul niveau dans une page</para>
</ssimplesect>
Liste d’élément
Exemple d’une liste simple

Habituellement utilisé avec 1 seule ligne par item.

<simplelist>
  <member>Premier élément de ma liste</member>
  <member>Deuxiéme élément</member>
  <member>Troisiéme ...</member>
  <member>4 iém ...</member>
  <member>et ainsi de suite...</member>
</simplelist>
Exemple de liste à point

Il y aura jutse un signe (un point)au début de chaque ligne.

<itemizedlist>
  <listitem><para>Premier élément de ma liste</para></listitem>
  <listitem><para>Deuxiéme élément</para></listitem>
  <listitem><para>Troisiéme ...</para></listitem>
  <listitem><para>4 iém ...</para></listitem>
  <listitem><para>et ainsi de suite...</para></listitem>
</itemizedlist>
  • Premier élément de ma liste

  • Deuxiéme élément

  • Troisiéme …​

  • 4 iém …​

  • et ainsi de suite…​

Exemple d’une liste numérotée

Il y aura un chiffre en début de chaque ligne.

<orderedlist>
  <listitem><para>Premier élément de ma liste</para></listitem>
  <listitem><para>Deuxiéme élément</para></listitem>
  <listitem><para>Troisiéme ...</para></listitem>
  <listitem><para>4 iém ...</para></listitem>
  <listitem><para>et ainsi de suite...</para></listitem>
</orderedlist>
  1. Premier élément de ma liste

  2. Deuxiéme élément

  3. Troisiéme …​

  4. 4 iém …​

  5. et ainsi de suite…​

Exemples de formattage de texte spécifique
sans formattage, de la syntaxe xml

Pour inclure une portion de code source java ou xml ou autre, il faut utiliser la balise programlisting

Dans le cas de code xml, pour empécher que ce code soit interprété, il faut, en plus, encadrer le code de

<![CDATA[
  <bla-bla> .... </bla-bla>
] ]>

dans l’exemple ci-dessus, il y a un espace de trop entre les deux ] pour empécher l’interprétation.

Faire ressortir du texte

Pour le formattage il y a deux notions :

  • du formattage en cours de texte

    <para>du formattage en <emphasis role="bold">cours</emphasis> de texte</para>
  • un paragraphe avec un formatage particulier, dans ce cas, il est possible de mettre un titre pour ce paragraphe, mais ce n’est pas obligatoire, en cas d’absence un titre par défaut sera affiché (en anglais).

Chaque formattage correspond à un style, actuellement 4 ont été défini

  • caution : prudence (ou attention mais plus faible que warning)

  • important : comme son nom l’indique

  • note : comme son nom l’indique

  • tip : astuce

  • warning : attention, mais plus fort que caution

Caution
Prudence

Avec le texte qui explique à quoi faire attention, le titre n’est pas obligatoire

<caution>
  <title>Prudence</title>
  <para>Avec le texte qui explique à quoi faire attention, le titre par défaut est caution</para>
</caution>
Important
Important

Voici le texte qui est important

<important>
  <title>Important</title>
  <para>Voici le texte qui est important</para>
</important>
Note
une Note

et voici le texte de la note, il peut inclure des section ou autre

<note>
  <title>une Note</title>
  <para>et voici le texte de la note, il peut inclure des section ou autre</para>
</note>
Tip
Astuce

Ne pas mettre le titre ou plutot inclure du formattage en cours de texte

<tip>
  <title>Astuce</title>
  <para>Ne pas mettre le titre ou plutot inclure du formattage en cours de texte</para>
</tip>
Warning
Attention

Attention, en voila enfin un sans titre

<warning><title>Attention</title>
  <para>Attention, en voila enfin un sans titre</para>
</warning>
Inclusion Possible

Lors de l’affichage d’une aide, en fonction des liens entre éléments de contenu, il est possible d’afficher plusieurs éléments de contenu

  • un autre fichier d’aide

  • une image

  • un formulaire (ou un écran) afin de pouvoir voir l’aide de chaque champ

inclusion d’un autre fichier d’aide

Il est possible de faire des inclusions de fichier à n’importe quel endroit selon la syntaxe xml (en générale), mais afin d’avoir un contrôle de la syntaxe en direct lors de la saisie de fichier xml au format docbook dans eclipse, le fichier xsd docbook (celui qui défini ce qui est autorisé) inclus dans ofbiz a été modifié pour inclure l’autorisation de la balise xi:include uniquement au niveau de chapter ou au niveau de section

L’inclusion est réalisé lors de la génération des fichiers html, le point de départ est donc bien la place du fichier docbook (ce n’est pas la même chose pour les fichiers image)

<xi:include href="../../../../../framework/documents/UnitTest.xml" />
inclusion d’une image

Il est possible d’insérer des images, la plupart du temps les images à insérer se trouve dans le répertoire image au même niveau que le fichier docbbok

Exemple d’affichage d’une image, Seul le champ fileref est obligoire, mais pour une bonne mise en page, le champs width est conseillés. De la même manière les balises textobject et caption sont importante.

Une page portail type
<mediaobject>
  <imageobject>
    <imagedata fileref="example_fr_images/MgmtPage_FR.png" width="100%"/>
  </imageobject>
  <textobject><phrase>Une page portail type</phrase></textobject>
  <caption>Schema d'une page portail type de gestion de recherche et gestion objet métier majeur</caption>
</mediaobject>
Tip
utiliser des images pas trop grosse

Il est conseillé d’utiliser des images ayant une taille de 600 en largueur et de les afficher avec le paramètre width exprimé en % pour que l’image se redimensionne en fonction de la taille de la page d’affichage

Pour convertir (et reduire) une image existante, l’utilitaire convert (sous linux) fonctionne trés bien

convert PortalPage_orig.png -strip -resize 600x450 -quality 70 -interlace line PortalPage.jpg
avec ofbizHelp, affichage de multiple page

Lors de l’affichage de l’aide via ofbizhelp:: sur la page d’accueil du composant example, 2 fichiers d’aide sont affichés, car les deux fichiers sont lié via le même "mapkey".

De la même manière, afin de permettre à l’utilisateur de pouvoir visualiser l’aide de chaque champ (qui apparait quand la souris est positionné sur le label du champ), il est intéressant, quelque fois, de faire apparaitre une form dans un texte d’aide. Pour cela, il faut créer un élément de contenu associé dataresource qui est associé à un screen.xml, avec un dataTemplateTypeId="SCREEN_COMBINED". Il est conseillé de faire un screen dédié qui n’inclus pas le decorator general.

Il faut ensuite associer l’élément de contenu avec le même père que l’aide avec lequel le screen doit s’afficher, mais avec un numéro de séquence supérieur. Voici les données pour l’affichage du screen ci-dessous.

<DataResource dataResourceId="EXAMPLE_EditForm"  objectInfo="component://example/widget/example/ExampleScreens.xml#ExampleEditForm" dataResourceTypeId="URL_RESOURCE" dataTemplateTypeId="SCREEN_COMBINED"/>
<Content contentId="EXAMPLE_EditForm" contentTypeId="DOCUMENT" contentName="Example Fields" dataResourceId="EXAMPLE_EditForm"/>
<ContentAssoc contentId="HELP_EXAMPLE_docbook" contentIdTo="EXAMPLE_EditForm" mapKey="EXAMPLE_docbook" fromDate="2006-01-12 01:01:01" sequenceNum="005" contentAssocTypeId="INSTANCE"/>
Exemple de syntaxe

Cette section regroupe des exemples de syntaxe docbook utilisé mais pas encore documenté en détail

Lien avec un terme du glossaire

exemple : ofbizhelp::

<glossterm linkend="G_OFBIZHELP">ofbizhelp</glossterm>
Lien vers un id interne

exemple : page d’accueil

<link linkend="about">page d'accueil</link>
Lien vers un site externe

exemple : Webhelp du projet docbook

<command xlink:href="http://wiki.docbook.org/WebHelp" xlink:show="new">Webhelp du projet docbook</command>

Glossary

addon pour OFBiz

Un addon pour Apache OFBiz c’est :

  • une fonction technique ou business, unitaire. Soit, elle peut être utilisée en tant que telle, soit elle est nécessaire pour plus de 2 autres addons,

  • son développement et sa maintenance n’est pas plus difficiles qu’un développement réalisé directement dans ofbiz,

  • techniquement, un seul fichier (une archive, zip ou jar ou …​) et une page de documentation.
    Le fichier archive contient l’ensemble des éléments de l’addon :

  • un ou plusieurs fichiers de modifications/ajouts à appliquer par fichier ofbiz modifié,

  • la documentation de l’addon,

  • la liste de ses dépendances ;

  • un nom unique,

  • est installable et désinstallable,

  • l’installation peut nécessiter qu’ofbiz soit arrêté,

  • il y a un mainteneur nommé,

  • la documentation doit signaler les paramétrages obligatoires s’il y en a.

adm Addon manager

L’addon manager permet d’installer ou de créer / modifier des addons pour Apache-OFBiz, il est utilisable en ligne de commande ou au travers de la page adm-gui dans le composant Adm. Sys.

Attribue de Portlet

Un attribue de portlet est un variable (et donc une valeur) qui peut être créé pour une portlet dans une page portail.
Cela permet de personnaliser son fonctionnement ou son aspect selon la page portail où elle est utilisé.
L’ajout d’un attribue (ou la modification de sa valeur) pour une portlet d’une page portail peut se faire via un formulaire dédié de la portlet, accessible via le bouton personalisation de la portlet dans la personalisation de la page portail. Il est aussi possible d’ajouter directement la valeur dans la table PortletAttribute, par exemple via le chargement d’un fichier data

<PortletAttribute portalPageId="ExampleMgmt" portalPortletId="ExampleStatus" portletSeqId="00001" attrName="helpAvailable" attrValue="N" />
Format docbook

Le format DocBook a été développé par le consortium OASIS spécifiquement pour la documentation technique. Il fournit un ensemble riche de balises pour décrire le contenu de document. Il est utilisé par énormément de projet de logiciel pour leur documentation.
Jusqu’à la version 17.12 c’est le format utilisé par Apache OFBiz pour les fichiers de documentation.

Objet métier

Un object métier correspond à une entité majeur de l’application. Cela correspond à une table de la base de donnée. Elle est associé à d’autres entités - les entités liés -.
Pour plus de détail sur les types d’entités / objets voir les entités utilisées dans le composant Example.

ofbizhelp, moteur d’aide utilisateur

Le moteur d’aide qui est inclus dans le noyau OFBiz utilise le moteur de gestion de contenu de OFBiz et construit l’aide de manière dynamique, mais pour des raisons de performance, l’interpréteur docbook utilisé dans ce cadre ne traite qu’un nombre réduit de type de balise docbook. Ce moteur d’aide est pratique lorsque l’aide doit pouvoir être modifiable par les utilisateurs.

Note
La documentation de ofbiz est actuellement en train d’être ré-organisé, ce qui fait que l’aide contextuel n’est, momentanément plus maintenu et donc un peu ancienne. Dans le future, cela incluera des liens vers la documentation (livre rédigé au format Asciidoc).
Webhelp, moteur d’aide utilisateur

Webhelp est le moteur d’aide OFBiz ajouté par l’addon webhelp en plus du moteur d’aide inclus dans le noyau ofbiz ofbizhelp. Le moteur webhelp d’OFBiz est issu du projet Webhelp du projet docbook. Il permet de générer un site d’aide (en html) à partir d’un ou de plusieurs fichiers au format docbook (donc plus maintenu, cf ci-dessus). Le nombre de type de balise docbook supporté est donc très étendu.

9.5. Web tools

9.6. Example

Le composant example est destiné aux développeurs pour avoir des examples de chacune des manières / techniques de développement avec le framework Apache OFBiz.

9.6.1. Objectif du composant Example

A quoi sert le composant exemple? comment l’utiliser ? quel est son objectif? …​ cette introduction est là pour répondre à toutes ces questions

L’application exemple vous permet de découvrir les possibilités pour créer ou modifier une application avec le framework fonctionnel et technique OFBiz. Il vous permettra de comprendre comment faire des écrans (ou portlet) avec, entre autres, des boîtes de recherche, des listes et beaucoup d’autres choses.

Cette application peut être utilisée pour avoir un aperçu des bonnes pratiques de développement d’interface utilisateur ou de service métier dans OFBiz. Le composant example doit être installé via copie du plugin example qui se trouve dans https://svn.apache.org/repos/asf/ofbiz/ofbiz-plugins/trunk/example

Dans le composant example nous retrouverons les sous-répertoire "classique" : cf Introduction / Les composants

Les entités utilisés
Diagramme UML des entités example

Dans ce composants, 6 entités (ou table de base de donnée) seront utilisées afin de reprendre les bonnes pratiques OFBiz en terme de définition de schéma de base de donnée pour une application.

Vous retrouverez ces cas dans toutes les application OFBiz.
En terme de terminologie on dira que :

  • Example : c’est une entity majeur ou objet métier ;

  • ExampleType : cette entité permet de typer les exemple, sur le plan fonctionnel un exemple a obligatoirement un type. La gestion de cette entité ne se fait que lors du paramétrage;

  • ExampleItem : c’est une entité secondaire ou entité lié (à un objet métier). C’est une composition (au sens UML) de Example, c’est à dire qu’un exampleItem ne peut exister que s’il est associé a un example, comme les lignes de commande ou les ligne de devis , ou …​;

  • ExampleStatus : contient l’historique des changements de statut des enregistrements example. La liste des statuts possible pour Exemple est stocké dans l’entité StatusItem, cette entité contient un champ statusTypeId qui aura une valeur spécifique "EXAMPLE_STATUS" pour tous les status possible pour Example.

  • ExampleFeature : caractéristique d’exemple, c’est une entité secondaire ou entité lié (à un objet métier), mais contrairement à ExampleItem c’est une entité associé (au sens UML) et non une composition. C’est à dire qu’il est possible de gérer les caractéristiques indépendamment des examples existant, puis d’associer à un example un ensemble de caractéristique, ou alors pour une caractéristique de trouver tous les examples qui lui sont associés;

  • ExampleFeatureAppli : cette entité permet de faire l’association entre un example et un exampleFeature, elle contient un champ "de la date" et "à la date" (fromDate et thruDate) qui correspondent au temps de validité de l’association.

10. Testing

10.1. Unit Tests

10.2. Integration Tests

10.3. UI Tests

Il existe un projet dédié "OFBiz® Selenium-WebDriver" (OfbSwd) dont le rôle est exclusivement les tests via l’interface utilisateur.

Cela couvre, aussi bien les tests unitaires de l’interface utilisateur que des tests de use case métier.

Pour plus de détail se référer à la documentation de ce projet

11. Deployment

12. Security

12.1. OFBiz in production

In matter of security, to be sure to be up to date, the first place to look at is https://ofbiz.apache.org/security.html

For more details you may be also interested by https://issues.apache.org/jira/browse/OFBIZ-1525

If you look for how to handle access permissions, this page should help you: https://cwiki.apache.org/confluence/display/OFBIZ/OFBiz+Security+Permissions

Last but not least, you will certainly find useful, the security section of The Apache OFBiz Technical Production Setup Guide

12.1.1. Gradle Update

OFBiz uses Gradle for many things, including building and running OFBiz.

Out Of The Box (OOTB) you get versions of third parties libraries which might need to be updated from time to time. For that you may take as an example to follow https://issues.apache.org/jira/browse/OFBIZ-10213

12.2. Passwords and JWT (JSON Web Tokens) usage

12.2.1. How are set and used passwords and JWT in Apache OFBiz

The Apache OFBiz Project Release trunk

Passwords

Demo and seed passwords are stored in files loaded through security ofbiz-component.xml. To know more about that be sure to read:

Caution
These configuration steps are not to be neglected for the security of a production environment
JWT usage

JSON Web Token (JWT) is an Internet standard for creating JSON-based access tokens that assert some number of claims.

We currently use JWT in 2 places:

  1. To let users safely recreate passwords (in backend and frontend)

  2. To allow SSO (Single Sign-on) jumpings from an OFBiz instance to another on another domain, by also using CORS ( Cross-origin resource sharing) on the target server

How to secure JWT

When you use JWT, in order to sign your tokens, you have the choice of using a sole so called secret key or a pair of public/private keys: https://jwt.io/introduction/.

You might prefer to use pair of public/private keys, for now by default OFBiz uses a simple secret key. Remains the way how to store this secret key. This is an interesting introduction about this question.

  1. The first idea which comes to mind is to use a property in the security.properties file. It’s safe as long as your file system is not compromised.

  2. You may also pick a SystemProperty entity (overrides the file property). It’s safe as long as your DB is not compromised.

  3. We recommend to not use an environment variable as those can be considered weak:

  4. You may want to tie the encryption key to the logged in user. This is used by the password recreation feature. The JWT secret key is salted with a combination of the current logged in user and her/his password. This is a simple and effective safe way.

  5. Use a JTI (JWT ID). A JTI prevents a JWT from being replayed. This auth0 blog article get deeper in that. The same is kinda achieved with the password recreation feature. When the user log in after the new password creation, the password has already been changed. So the link (in the sent email) containing the JWT for the creation of the new password can’t be reused.

  6. Tie the encryption key to the hardware. You can refer to this Wikipedia page for more information.

  7. If you want to get deeper in this get to this OWASP documentation

Note: if you want to use a pair of public/private keys you might want to consider leveraging the Java Key Store that is also used by the "catalina" component to store certificates. Then don’t miss to read:

Also remember that like everything a JWT can be attacked and, though not used or tried in OFBiz yet, a good way is to mitigate an attack by using a KeyProvider. I have created OFBIZ-11187 for that.

Properties

The security.properties file contains five related properties:

# -- If false, then no externalLoginKey parameters will be added to cross-webapp urls
security.login.externalLoginKey.enabled=true
# -- Security key used to encrypt and decrypt the autogenerated password in forgot password functionality.
#    Read Passwords and JWT (JSON Web Tokens) usage documentation to choose the way you want to store this key
login.secret_key_string=login.secret_key_string
# -- Time To Live of the token send to the external server in seconds
security.jwt.token.expireTime=1800
# -- Enables the internal Single Sign On feature which allows a token based login between OFBiz instances
# -- To make this work you also have to configure a secret key with security.token.key
security.internal.sso.enabled=false
# -- The secret key for the JWT token signature. Read Passwords and JWT (JSON Web Tokens) usage documentation to choose the way you want to store this key
security.token.key=security.token.key

There are also SSO related SystemProperties in SSOJWTDemoData.xml:

    <SystemProperty systemResourceId="security" systemPropertyId="security.internal.sso.enabled" systemPropertyValue="false"/>
    <SystemProperty systemResourceId="security" systemPropertyId="security.token.key" systemPropertyValue="security.token.key"/>
    <SystemProperty systemResourceId="security" systemPropertyId="SameSiteCookieAttribute" systemPropertyValue="strict"/>
Internal SSO

The introduction of the same-site attribute set to 'strict' for all cookies prevents the internal Single Sign On feature. Why is clearly explained here.

So same-site attribute set to 'none' is necessary for the internal SSO to work, 'lax' is not enough. So if someone wants to use the internal SSO feature s/he also needs to use the CSRF token defense. If s/he wants to be safe from CSRF attacks. Unfortunately, due backporting difficulties, this option is currently (2020-04-15) only available in trunk.

Fecth API

An alternative would be to use the Fetch Javascript API with the

credentials: "include"

For those interested, there are more information in https://issues.apache.org/jira/browse/OFBIZ-11594

Last but not least

Be sure to read Keeping OFBiz secure

12.3. CSRF defense

12.3.1. How is done the CSRF defense in Apache OFBiz and how to adapt it if needed

The Apache OFBiz Project Release trunk

The same-Site attribute

The SameSite attribute is an effective counter measure to cross-site request forgery, cross-site script inclusion, and timing attacks.

— According to OWASP ZAP

By default OOTB the SameSiteFilter property sets the same-site attribute value to 'strict. SameSiteFilter allows to change to 'lax' if needed. If you use 'lax' we recommend that you set the csrf.defense.strategy property to org.apache.ofbiz.security.CsrfDefenseStrategy in order to provide an effective defense against CSRF attacks.

Properties

The security.properties file contains related properties:

# -- By default the SameSite value in SameSiteFilter is 'strict'.
# -- This property allows to change to 'lax' if needed.
# -- If you use 'lax' we recommend that you set
# -- org.apache.ofbiz.security.CsrfDefenseStrategy
# -- for csrf.defense.strategy (see below)
SameSiteCookieAttribute=
# -- The cache size for the Tokens Maps that stores the CSRF tokens.
# -- RemoveEldestEntry is used when it's get above csrf.cache.size
# -- Default is 5000
# -- TODO: possibly separate tokenMap size from partyTokenMap size
csrf.cache.size=
# -- Parameter name for CSRF token. Default is "csrf" if not specified
csrf.tokenName.nonAjax=
# -- The csrf.entity.request.limit is used to show how to avoid cluttering the Tokens Maps cache with URIs starting with "entity/"
# -- It can be useful with large Database contents, ie with a large numbers of tuples, like "entity/edit/Agreement/10000, etc.
# -- The same principle can be extended to other cases similar to "entity/" URIs (harcoded or using similar properties).
# -- Default is 3
csrf.entity.request.limit=
# -- CSRF defense strategy.
# -- Because OFBiz OOTB also sets the SameSite attribute to 'strict' for all cookies,
# -- which is an effective CSRF defense,
# -- default is org.apache.ofbiz.security.NoCsrfDefenseStrategy if not specified.
# -- Use org.apache.ofbiz.security.CsrfDefenseStrategy
# -- if you need to use a 'lax' for SameSiteCookieAttribute
csrf.defense.strategy=

There is also a SystemProperty in SSOJWTDemoData.xml:

<SystemProperty systemResourceId="security" systemPropertyId="SameSiteCookieAttribute" systemPropertyValue="strict"/>

12.4. Impersonation

12.4.1. What is Impersonation in Apache OFBiz

The Apache OFBiz Project Release trunk

Introduction to User impersonation

User Impersonation is a feature that offer a way to select a user login and impersonate it, i.e. see what the user could see navigating through the application in his name.

How do this work ?

An authorized user (see security and controls section for configuration), can select a user that will be impersonated.

The impersonation start, if everything is well configured, in current application (partymgr for the demo). Everything appears like if we were logged in with the userLoginId and the valid password (though we know nothing about it)

The only thing showing that we currently are impersonating a user is the little bottom-right image :

Impersonate icon

This icon indicates, when clicking on it, the user impersonated, and offer a way to depersonate.

The impersonate period is stored for audit purpose, and if the impersonator forgot to depersonate, the period is terminated one hour after impersonation start.

Security

This feature can draw some concerns about security aspect. This paragraph will introduce every controls and properties that have been implemented around the impersonation feature.

Caution
These configuration steps are not to be neglected for a production environment since this feature offer a way to act in place of another user.
Properties

The security.properties file introduce two properties that control impersonation feature :

security.disable.impersonation = true

This property, set by default to true, controls the activation of impersonation feature. If no configuration is done any user trying to use impersonation will face an error message, indicating that the feature is disabled.

To enable impersonation this property need to be set to false

security.login.authorised.during.impersonate = false

This property controls the way impersonation occurred to the impersonated user :

In default configuration, the impersonated user see nothing and can use the application without knowing that he is currently impersonated. Several authorized user can impersonate a same login without any issue.

Note
This configuration is intended for testing/QA environment allowing any authorized user to impersonate a login to validate its configuration, test the application etc.

Set to true, this configuration improve the control of the data generated by the impersonated user. Indeed, Only one authorized user can impersonate a login at the same time, and during the impersonation process, the impersonated user is unable to act within the application.

Since the impersonation period is stored in database, the actions done by the authorized user can be identified if there is the need to do so.

Note
This configuration is intended for production environment
Controls
The permission

First, to be able to use impersonation, a user need to possess IMPERSONATE_ADMIN permissions. Demo data offer IMPERSONATION security group for this purpose.
In demo data, FULLADMIN security group also possess the permission.

Permission based user restriction

An authorized user cannot impersonate any user. There are two main controls that will restrict the impersonation feature.

Cannot impersonate Admin user

It is impossible to impersonate a user that is granted any of the admin permission :

"IMPERSONATE_ADMIN"
"ARTIFACT_INFO_VIEW"
"SERVICE_MAINT"
"ENTITY_MAINT"
"UTIL_CACHE_VIEW"
"UTIL_DEBUG_VIEW"
Cannot impersonate more privileged user

It is impossible to impersonate a user that has more permission than your user. Even if the missing persmission is a minor one.

12.5. Gradle Dependency Verification

The Apache OFBiz Project Release trunk

Caution
This feature is for now disabled. You may use it locally if you want…​

As it’s a long read you might prefer this summary:

Note
the dependency verification is an incubating feature. So we will wait before backporting from trunk…​

By default OFBiz comes with OOTB Gradle dependency verification.

This means that it embeds a verification-metadata.xml file and a verification-keyring.gpg in OFBiz gradle sub-directory which is used during builds and other tasks to verify dependencies.

These files are initially created using :

Tip
gradlew --write-verification-metadata pgp,sha256 help
gradlew --write-verification-metadata pgp,sha256 --export-keys

These command creates or updates the verification-metadata.xml and verification-keyring.gpg files which respectively contains the checksums for each of declared dependencies and the related keys

Currently the status is it’s incomplete in OFBiz. You get this message:

  • Some artifacts aren’t signed or the signature couldn’t be retrieved.

  • Some signature verification failed. Checksums were generated for those artifacts but you MUST check if there’s an actual problem. Look for entries with the following comment: PGP verification failed PGP verification failed

Only 6 keys are concerned. This does not prevent the verification to work using metadata, though it’s better to check the situation in case of doubts (OK OTTB). You may use

Tip
gradlew build --refresh-keys

To recreate the keys

The verification-metadata.xml file contains 2 entries that can be set to true or false to check or ignore the 2 functionalities:

Important
<verify-metadata>true</verify-metadata>
<verify-signatures>true</verify-signatures>

Finally, you may refer to https://issues.apache.org/jira/browse/OFBIZ-12186 for more information.

13. Appendices

14. From Mini Language to Groovy

This is a small guide for everybody involved in converting the Mini Language into Groovy.

Note
Why is this important?
This tutorial is directly linked to the efforts of converting all scripts in Mini Language to newer Groovy Scripts. All of this is done, because Groovy is much more readable and easier to review, more up to date and many other reasons, which can be found here: Proposal for deprecating Mini Language

To contribute, or just be up to date with the current process, you can look at the existing JIRA issue OFBIZ-9350 - Deprecate Mini Lang

14.1. Groovy DSL (dynamic scripting library)

14.1.1. How to get Groovy support in your IDE

The following paragraph is for Eclipse users.

It is possible to get Groovy support in Eclipse by converting the loaded project to a Groovy Project. The project itself will work as before.

To do this just follow these few steps:

  1. Right-click on the project that has to be converted

  2. Click on "Configure"

  3. Click on "Convert to Groovy Project"

Eclipse will automatically load the file OfbizDslDescriptorForEclipse.dsld , in which the known fields and methods used in Groovy Scripts are defined.

14.1.2. Known Fields

property name: 'parameters'
type : 'java.util.Map'

These are the parameters given to the Groovy Script, when it is called as a service. It is equivalent to Map<String, Object> context in the Java-Service-Definition.

property name: 'context'
type: 'java.util.Map'

More parameters, which are, for example, given through a screen or another Groovy Script. This is important when the script is called through an action segment of a screen.

property name: 'delegator'
type: 'org.apache.ofbiz.entity.Delegator'

Normal instance of the Delegator, which is used for special database access.

property name: 'dispatcher'
type: 'org.apache.ofbiz.service.LocalDispatcher'

Normal instance of the LocalDispatcher, which is used to call services and other service-like operations.

property name: 'security'
type: 'org.apache.ofbiz.security.Security'

Normal instance of the Security-Interface with which permission checks are done.

14.2. Known Methods

method name: 'runService'
type: 'java.util.Map'
params: [serviceName: 'String', inputMap: 'java.util.Map']

Helping method to call services instead of dispatcher.runSync(serviceName, inputMap). Also possible: run service: serviceName, with: inputMap

method name: 'makeValue'
type: 'java.util.Map'
params: [entityName: 'String']

Helping method to make a GenericValue instead of delegator.makeValue(entityName). Creates an empty GenericValue of the specific entity.

method name: 'findOne'
type: 'java.util.Map'
params: [entityName: 'String', inputMap: 'java.util.Map']

Helping method to find one GenericValue in the database. Used instead of delegator.findOne(entityName, inputMap)

method name: 'findList'
type: 'java.util.List'
params: [entityName: 'String', inputMap: 'java.util.Map']

Helping method to find many GenericValue in the database. Used instead of delegator.findList(entityName, inputMap, null, null, null, false)

method name: 'select'
type: 'org.apache.ofbiz.entity.util.EntityQuery'
params: [entity: 'java.util.Set']

Helping method used instead of EntityQuery.use(delegator).select(…​)

method name: 'select', type: 'org.apache.ofbiz.entity.util.EntityQuery', params: [entity: 'String…​']
As above.

method name: 'from'
type: 'org.apache.ofbiz.entity.util.EntityQuery'
params: [entity: 'java.lang.Object']

Helping method used instead of EntityQuery.use(delegator).from(…​)

method name: 'success'
type: 'def'
params: [message: 'String']

Helping method used instead of ServiceUtil.returnSuccess(message)

method name: 'failure'
type: 'java.util.Map'
params: [message: 'String']

Helping method used instead of ServiceUtil.returnFailure(message)

method name: 'error'
type: 'def'
params: [message: 'String']

Helping method used instead of ServiceUtil.returnError(message)

method name: 'logInfo'
type: 'void'
params: [message: 'String']

Helping method used instead of Debug.logInfo(message, fileName)

method name: 'logWarning'
type: 'void'
params: [message: 'String']

Helping method used instead of Debug.logWarning(message, fileName)

method name: 'logError'
type: 'void'
params: [message: 'String']

Helping method used instead of Debug.logError(message, fileName)

method name: 'logVerbose'
type: 'void'
params: [message: 'String']

Helping method used instead of Debug.logVerbose(message, fileName)

The actual definition of the methods can be found in `/framework/service/src/main/java/org/apache/ofbiz/service/engine/GroovyBaseScript.groovy, the variables dctx, dispatcher and delegator are set in the file GroovyEngine.java which can be found in the same location.

14.3. Services

14.3.1. From MiniLang to Groovy

To see additional examples and finished conversions, which may help with occurring questions, click: OFBIZ-9350 - Deprecate Mini Lang There is a chance that a similar case has already been converted.

Important
When a simple-method ends, it will automatically at least return a success-map.

All the Groovy Services have to return success at least, too.

return success()

14.3.2. Getting started

MiniLang files consist of services, which, in most cases, implement services.

The get converted to Groovy like the following:

<!-- This is MiniLang -->
<simple-method method-name="createProductCategory" short-description="Create an ProductCategory">
   <!-- Code -->
</simple-method>
// This is the converted Groovy equivalent
/**
 * Create an ProductCategory
 */
def createProductCategory() {
    // Code
}

It will be useful for future developers, and everybody who has to check something in the code, to put at least the short-description as the new Groovydoc. This will hopefully more or less explain, what the method should or shouldn’t do. If the short-description isn’t helpful enough, feel free complete it.

The structure of if and else in MiniLang is a little different than the one from Groovy or Java and can be a bit confusing when first seen, so here is an example:

<if-empty field="parameters.productCategoryId">
    <sequenced-id sequence-name="ProductCategory" field="newEntity.productCategoryId"/>
<else>
    <set field="newEntity.productCategoryId" from-field="parameters.productCategoryId"/>
    <check-id field="newEntity.productCategoryId"/>
    <check-errors/>
</else>
</if-empty>
Note
Notice, that the else always starts before the if-tag is closed, but sometimes isn’t indented as one would expect it.

When navigating through bigger if-phrases, the navigation itself will be much easier through just clicking in the opening or closing if-tag; Eclipse will automatically mark the matching opening or closing if-tag for you.

There are two possibilities to initialize a field/variable in Groovy.

  1. To define a field/variable with its correct typing
    String fieldName = "value"`

  2. To just "define" a field/variable. The IDE you are working with may not recognize the typing, but OFBiz can work with it:
    def fieldName = "value"

14.4. Checking Fields

Minilang Groovy
<if-empty field="fieldName"></if-empty>
 //checks if fieldName is existent and/or empty
if (!fieldName) {}
<if-empty field="fieldName.property"></if-empty>
 // fieldName has to be existent, property doesn't need to
 // if known, that property does exist, the ? can be left out
if (!fieldName?.property) {}
 // CAUTION: every query like this in Groovy evaluates to a Boolean type
 // everything that is empty or false will turn into false:
 // null, [], [:], "", false -> false

if (UtilValidate.isEmpty(fieldName)) {}
<if>
    <condition>
        <or>
            <if-empty field="field1"/>
            <if-empty field="field2"/>
        </or>
    </condition>
    <then>
        <!-- code in if -->
    </then>
    <else>
        <!-- code in else -->
    </else>
</if>
if (!field1 || !field2) {
 // code in if
} else {
 // code in else
}
<if-compare-field field="product.primaryProductCategoryId" to-field="parameters.productCategoryId" operator="equals">
    <!-- code -->
</if-compare-field>
 // this will even work, if product is not existent or null
if (UtilValidate.areEqual(product?.primaryProductCategoryId, parameters.productCategoryId)) {
    // code
}
<if-instance-of field="parameters.categories" class="java.util.List"></if-instance-of>
if (parameters.categories instanceof java.util.List) {}

14.5. Setting Fields

Minilang Groovy
<set field="fieldName" value="value"/>
 // if fieldName is not initialized
String fieldName = "value"
 // if fieldName is initialized
fieldName = "value"
<set field="otherFieldName.property" value="value"/>
<set field="otherFieldName.otherProperty" value="true" type="Boolean"/>
<set field="otherFieldName.otherProperty" from-field="parameters.property/>
 // if otherFieldName is not yet initialized, you have to do it first
 // MiniLang does that automatically
Map otherFieldName = [:] // empty Map
 // now put the values in
otherFieldName = [
    property: "value",
    otherProperty: true
]
 // or the less efficient way
otherFieldName.property = "value"
otherFieldName.otherProperty = true

 // it is possible to put different values in later:
otherFieldName.property = parameters.property
<set field="thisFieldName" value="${groovy: []}" type="List"/>
 // this is easier in Groovy
List thisFieldName = []
<property-to-field resource="CommonUiLabels" property="CommonGenericPermissionError" field="failMessage"/>
<!-- there are different cases of this, which are not distinguished in MiniLang -->
<property-to-field resource="general.properties" property="currency.uom.id.default" field="parameters.rateCurrencyUomId"/>
String failMessage = UtilProperties.getMessage("CommonUiLabels", "CommonGenericPermissionError", parameters.locale)
 // in Groovy there can be a difference for the second case
parameters.rateCurrencyUomId = UtilProperties.getPropertyValue('general.properties', 'currency.uom.id.default')
<clear-field field="product.primaryProductCategoryId"/>
product.primaryProductCategoryId = null

14.6. Starting Services

Minilang Groovy
<set field="relatedCategoryContext.parentProductCategoryId"  from-field="defaultTopCategoryId"/>
<call-service service-name="getRelatedCategories" in-map-name="relatedCategoryContext">
    <result-to-field result-name="categories" field="resCategories"/>
</call-service>
def relatedCategoryContext = [parentProductCategoryId: defaultTopCategoryId]
def serviceResult = run service: "getRelatedCategoryies", with: relatedCategoryContext
def resCategories = serviceResult.categories
 // if it is not too confusing to read you can leave out the extra variable
run service: "getRelatedCategoryies", with: [parentProductCategoryId: defaultTopCategoryId]
<set-service-fields service-name="productCategoryGenericPermission" map="parameters" to-map="productCategoryGenericPermissionMap"/>
<call-service service-name="productCategoryGenericPermission" in-map-name="productCategoryGenericPermissionMap">
    <results-to-map map-name="genericResult"/>
</call-service>
 // instead of setting the service fields from parameters, it is possible to run the service with the parameters map
Map genericResult = run service: "productCategoryGenericPermission", with: parameters

14.7. Preparing Service Results

Minilang Groovy
<field-to-result field="fieldBudgetId" result-name="budgetId"/>
 // MiniLang knows this implicitly
def result = success()
result.budgetId = fieldBudgetId
return result

14.8. Database Communication

Minilang Groovy
<make-value entity-name="FinAccountTrans" value-field="newEntity"/>
<set-nonpk-fields map="parameters" value-field="newEntity"/>
<set-pk-fields map="parameters" value-field="newEntity"/>
 // this is the easy way
GenericValue newEntity = makeValue("FinAccountTrans", parameters)
 // this is also possible
GenericValue newEntity = makeValue("FinAccountTrans")
newEntity.setPKFields(parameters)
newEntity.setNonPKFields(parameters)
<entity-and entity-name="BudgetStatus" list="budgetStatuses">
    <field-map field-name="budgetId" from-field="parameters.budgetId"/>
    <order-by field-name="-statusDate"/>
</entity-and>
 // this can also be done in one line, but it can easily become unreadable
def budgetStatuses = from("BudgetStatus")
    .where("budgetId", paramters.budgetId)
    .orderBy("-statusDate")
    .queryList()
<entity-one entity-name="StatusValidChange" value-field="statusValidChange">
    <field-map field-name="statusId" from-field="budgetStatus.statusId"/>
    <field-map field-name="statusIdTo" from-field="parameters.statusId"/>
</entity-one>
<!-- entity-one can be called without child elements, too -->
<entity-one entity-name="Product" value-field="product" auto-field-map="true"/>
 // MiniLang has false set for useCache as the default value
statusValidChange = findOne("StatusValidChange", [statusId: budgetStatus.statusId, statusIdTo: parameters.statusId], false)
 // this is also possible
statusValidChange = from("StatusValidChange")
    .where("statusId", budgetStatus.statusId, "statusIdTo", parameters.statusId)
    .queryOne()
 // if there are no child elements, this can be used
GenericValue product = from("Product").where(parameters).queryOne()
<find-by-primary-key entity-name="ProductCategoryMember" map="lookupPKMap" value-field="lookedUpValue"/>
GenericValue lookedUpValue = findOne("ProductCategoryMember", lookupPKMap, false)
 // this is also possible
lookedUpValue = from("ProductCategoryRole")
    .where(lookupPKMap)
    .queryOne()
<entity-condition entity-name="ProductCategoryContentAndInfo" list="productCategoryContentAndInfoList" filter-by-date="true" use-cache="true">
    <condition-list combine="and">
        <condition-expr field-name="productCategoryId" from-field="productCategoryList.productCategoryId"/>
        <condition-expr field-name="prodCatContentTypeId" value="ALTERNATIVE_URL"/>
    </condition-list>
    <order-by field-name="-fromDate"/>
</entity-condition>
<!-- entity-condition can also be used with the "or" operator -->
<entity-condition entity-name="ProdCatalogCategory" list="prodCatalogCategoryList" filter-by-date="true">
    <condition-list combine="and">
        <condition-expr field-name="productCategoryId" from-field="parameters.productCategoryId"/>
        <condition-list combine="or">
            <condition-expr field-name="prodCatalogCategoryTypeId" value="PCCT_VIEW_ALLW"/>
            <condition-expr field-name="prodCatalogCategoryTypeId" value="PCCT_PURCH_ALLW"/>
        </condition-list>
    </condition-list>
</entity-condition>
 // the Groovy methods use the "and" and "equals" operator as default values
List productCategoryContentAndInfoList = from("ProductCategoryContentAndInfo")
    .where("productCategoryId", productCategoryList.productCategoryId, "prodCatContentTypeId", "ALTERNATIVE_URL")
    .cache().orderBy("-fromDate")
    .filterByDate()
    .queryList()
 // with the use of the "or" operator you have to build your condition like this
EntityCondition condition = EntityCondition.makeCondition([
    EntityCondition.makeCondition([
        EntityCondition.makeCondition("prodCatalogCategoryTypeId", "PCCT_VIEW_ALLW"),
        EntityCondition.makeCondition("prodCatalogCategoryTypeId", "PCCT_PURCH_ALLW")
    ], EntityOperator.OR),
    EntityCondition.makeCondition("productCategoryId", parameters.productCategoryId)
])
List prodCatalogCategoryList = from("ProdCatalogCategory").where(condition).filterByDate().queryList()
<make-value entity-name="FinAccountTrans" value-field="newEntity"/>
<set-nonpk-fields map="parameters" value-field="newEntity"/>
<!-- In this case multiple fields of the GenericValue are set -->
<make-value entity-name="ProductCategoryRollup" value-field="newLimitRollup"/>
<set field="newLimitRollup.productCategoryId" from-field="newEntity.productCategoryId"/>
<set field="newLimitRollup.parentProductCategoryId" from-field="productCategoryRole.productCategoryId"/>
<set field="newLimitRollup.fromDate" from-field="nowTimestamp"/>
def newEntity = makeValue("FinAccountTrans", parameters)
 // you can set multiple fields of a GenericValue like this
def newLimitRollup = makeValue("ProductCategoryRollup", [
    productCategoryId: newEntity.productCategoryId,
    parentProductCategoryId: productCategoryRole.productCategoryId,
    fromDate: nowTimestamp
])
<set field="statusValidChange.prop" value="value"/>
statusValidChange.prop = "value"
<create-value value-field="newEntity"/>
newEntity.create()
<store-value value-field="newEntity"/>
<store-list list="listToStore"/>
newEntity.store()
delegator.storeAll(listToStore)
<clone-value value-field="productCategoryMember" new-value-field="newProductCategoryMember"/>
def newProductCategoryMember = productCategoryMember.clone()
<remove-value value-field="lookedUpValue"/>
lookedUpValue.remove()
<sequenced-id sequence-name="ProductCategory" field="newEntity.productCategoryId"/>
newEntity.productCategoryId = delegator.getNextSeqId("ProductCategory")
<check-id field="newEntity.productCategoryId"/>
UtilValidate.checkValidDatabaseId(newEntity.productCategoryId)
<make-next-seq-id value-field="newEntity" seq-field-name="linkSeqId"/>
delegator.setNextSubSeqId(newEntity, "linkSeqId", 5, 1)
 // the numbers 5 and 1 are used in the Java implementation of the MiniLang method
 // and can also be found as the default values in the MiniLang documentation

14.9. Permissions

Caution
To also check for admin-permissions, this method has to be used:
hasEntityPermission(permission, action, userLogin)

If the method is used with wildcards, it is important to not forget the underscore, which comes before the parameter action!

Minilang Groovy
<check-permission permission="CATALOG" action="_CREATE">
    <alt-permission permission="CATALOG_ROLE" action="_CREATE"/>
    <fail-property resource="ProductUiLabels" property="ProductCatalogCreatePermissionError"/>
</check-permission>
<check-errors/>
if (!(security.hasEntityPermission("CATALOG", "_CREATE", parameters.userLogin)
    || security.hasEntityPermission("CATALOG_ROLE", "_CREATE", parameters.userLogin))) {
    return error(UtilProperties.getMessage("ProductUiLabels", "ProductCatalogCreatePermissionError", parameters.locale))
}
<set field="hasCreatePermission" value="false" type="Boolean"/>
<if-has-permission permission="${primaryPermission}" action="${mainAction}">
    <set field="hasCreatePermission" value="true" type="Boolean"/>
</if-has-permission>
 // this will automatically be set to false if the user doesn't have the permission
def hasCreatePermission = security.hasEntityPermission(primaryPermission, "_${mainAction}", parameters.userLogin)

14.10. Timestamp And System Time

The first two simple-method are deprecated; the third method should have been used instead.

Minilang Groovy
<now-timestamp field="nowTimestamp"/>
Timestamp nowTimestamp = UtilDateTime.nowTimestamp()
<now-date-to-env field="nowDate"/>
Timestamp nowDate = UtilDateTime.nowTimestamp()
<!-- this method also has the parameter "type", which is set to 'java.sql.timestamp' as default -->
<now field="fooNow"/>
Timestamp fooNow = UtilDateTime.nowTimestamp()
<if-compare-field field="productCategoryMember.thruDate" to-field="expireTimestamp" operator="less" type="Timestamp">
    <!-- code -->
</if-compare-field>
Timestamp thruDate = productCategoryMember.thruDate
if (thruDate && thruDate.before(expireTimestamp)) {
    // code
}

14.11. Logging

Since all of the log methods are know to the Groovy Language, it is possible to just nearly use them as they are in MiniLang.
For further explanation, here are some examples:

Minilang Groovy
<log level="verbose" message="Permission check failed, user does not have permission"/>
logVerbose("Permission check failed, user does not have the correct permission.")
<log level="info" message="Applying feature [${productFeatureId}] of type [${productFeatureTypeId}] to product [${productId}]"/>
logInfo("Applying feature [${productFeatureId}] of type [${productFeatureTypeId}] to product [${productId}]")

14.12. General

Minilang Groovy
<call-simple-method method-name="checkCategoryRelatedPermission"/>
<check-errors/>
 // simple-methods inside of classes, as long as they are not services, will be called like normal methods
Map res = checkCategoryRelatedPermission("updateProductCategory", "UPDATE", null, null)
if (!ServiceUtil.isSuccess(res)) {
    return res
}
<iterate list="subCategories" entry="subCategory">
    <!-- code -->
</iterate>
for (def subCategory : subCategories) {
    // code
}
subCategories.each { subCategory ->
    // code
}
<iterate-map map="parameters.productFeatureIdByType" key="productFeatureTypeId" value="productFeatureId">
    <!-- in here something should happen with value and key -->
</iterate-map>
for (Map entry : parameters.productFeatureIdByType.entrySet()) {
    def productFeatureTypeId = entry.getKey()
    def productFeatureId = entry.getValue()
    // in here something should happen with value and key
}
<if>
    <condition>
        <not>
            <or>
                <if-has-permission permission="CATALOG" action="_${checkAction}"/>
                <and>
                    <if-has-permission permission="CATALOG_ROLE" action="_${checkAction}"/>
                    <not><if-empty field="roleCategories"/></not>
                </and>
            </or>
        </not>
    </condition>
    <then>
        <!-- code -->
    </then>
</if>
if (!security.hasEntityPermission("CATALOG", "_${checkAction}", parameters.userLogin)
    && !(security.hasEntityPermission("CATALOG_ROLE", "_${checkAction}", parameters.userLogin)
    && roleCategories)) {
    // code
}
<set field="validDate" from-field="parameters.validDate"/>
<if-not-empty field="validDate">
    <filter-list-by-date list="productCategoryMembers" valid-date="validDate"/>
</if-not-empty>
def query = from("ProductCategoryMember").where("productCategoryId", parameters.productCategoryId)
if (parameters.validDate) {
    query.filterByDate()
}
List productCategoryMembers = query.queryList()
<order-map-list list="productsList">
    <order-by field-name="sequenceNum"/>
</order-map-list>
productsList = EntityUtil.orderBy(productsList, ["sequenceNum"])

14.13. Where to find MiniLang implementation

If you find yourself in a position, where you don’t know how to convert a certain tag from MiniLang to Groovy, you can always check the Java implementation of the MiniLang method.
All of the methods have an existing Java implementation and you can find all of them in this folder: /ofbiz/trunk/framework/minilang/src/main/java/org/apache/ofbiz/minilang/method

The interesting part of this implementation is the method exec(), which actually runs the MiniLang tag.
The tag <remove-by-and> for example is realized using this part of code here:

@Override

public boolean exec(MethodContext methodContext) throws MiniLangException {
    @Deprecated
    String entityName = entityNameFse.expandString(methodContext.getEnvMap());
    if (entityName.isEmpty()) {
        throw new MiniLangRuntimeException("Entity name not found.", this);
    }
    try {
        Delegator delegator = getDelegator(methodContext);
        delegator.removeByAnd(entityName, mapFma.get(methodContext.getEnvMap()));
    } catch (GenericEntityException e) {
        String errMsg = "Exception thrown while removing entities: " + e.getMessage();
        Debug.logWarning(e, errMsg, module);
        simpleMethod.addErrorMessage(methodContext, errMsg);
        return false;
    }
    return true;
}

In this you can find one important part of code, which is:

delegator.removeByAnd(entityName, mapFma.get(methodContext.getEnvMap()));

This tells you, that, if you’re trying to convert the tag <remove-by-and>, you can use delegator.removeByAnd() in Groovy.