Création de module et exécution de hooks

Bonjour à tous,

Je suis actuellement en train de créer un nouveau module permettant de passer des commandes entre les entités Dolibarr (Multicompany), mais je n’arrive pas à faire fonctionner mon hook.

J’essaie de récupérer le hook ‹ supplierordercard › que j’ajoute dans la clé ‹ hooks › de ‹ $this->module_parts › dans le fichier ‹ modOrdersBetweenEntites › de cette façon :

‹ hooks › => array(
‹ data › => array(
‹ ordersuppliercard ›,
‹ globalcard ›
),
‹ entity › => ‹ 0 ›
),

J’ai également créé un fichier ‹ actions_ordersbetweenentites.class.php › dans lequel j’ajoute la classe ‹ ActionsOrderbetweenentities › avec un constructeur et une méthode ‹ addMoreActionsButtons ›.

J’ai affiché les fichiers d’actions appelés depuis la fiche des commandes fournisseur concernés par des hooks, mais malheureusement, mon fichier n’en fait pas partie.

J’ai tout essayé, mais je n’y arrive pas. Quelqu’un aurait-il une solution à me proposer ?

Merci d’avance,
Melbarkani

Bonjour,

Pour les hooks, je vous conseille le tuto Patas-Monkey sur youtube qui est plutôt complet et sinon la documentation dolibarr peut également vous aider.

Il me semble que vous n’avez pas besoin de mettre autant de choses dans votre array hooks.
'hooks' => array('ordersuppliercard', 'globalcard'), devrait suffire.

Bonne journée

Merci pour votre réponse rapide. J’ai essayé les deux méthodes (avec et sans le data), j’ai également regardé la vidéo et suivi la documentation Dolibarr, mais ça ne fonctionne toujours pas.

Je précise également que je suis sur la version 13.0.2

Est-il possible de voir votre fichier action_ordersbetweenentities.class.php ?

oui, voici :

<?php

class ActionsOrdersBetweenEntities
{
    public $db;
    public $error;

    public function __construct($db)
    {
        $this->db = $db;
    }

    /**
     * Hook for adding a button on the supplier order card
     */
    public function addMoreActionsButtons($parameters, &$object, &$action, $hookmanager)
    {
        global $langs, $user;

        if ($this->shouldDisplayButton($object, $user)) {
            $langs->load("ordersbetweenentities@ordersbetweenentities");

            $buttonHtml = $this->getCreateCustomerOrderButton($object);
            $parameters['resprints'] .= $buttonHtml;
            print $buttonHtml;
        }

        return 0;
    }

    /**
     * Determine if the button should be displayed
     */
    private function shouldDisplayButton($object, $user): bool
    {
        return !empty($object->id)
            && $user->rights->commande->creer
            && in_array(
                $object->statut,
                [
                    CommandeFournisseur::STATUS_ACCEPTED,
                    CommandeFournisseur::STATUS_ORDERSENT,
                    CommandeFournisseur::STATUS_RECEIVED_PARTIALLY,
                    CommandeFournisseur::STATUS_RECEIVED_COMPLETELY
                ]
            );
    }

    /**
     * Generate the HTML for the button and JavaScript
     */
    private function getCreateCustomerOrderButton($object): string
    {
        global $langs;
        
        $buttonId = 'btnCreateCustomerOrder';
        $buttonLabel = dol_escape_htmltag($langs->trans("CreateCustomerOrder"));
        $ajaxUrl = dol_buildpath('/multicompany/core/ajax/functions.php', 1);
        $scriptUrl = dol_buildpath('/custom/entityorderflow/scripts/create_customer_order.php', 1);
        $token = currentToken();
        $sourceOrderId = (int) $object->id;
        $targetEntityId = 1; // Rendre ceci dynamique si l'on veut au contraire créer des commandes depuis bleu vers rouge

        return <<<HTML
<a id="{$buttonId}" class="butAction" href="#">{$buttonLabel}</a>
<script>
    $(document).ready(function () {
        $("#{$buttonId}").on("click", function () {
            $.post("{$ajaxUrl}", {
                action: "switchEntity",
                id: {$targetEntityId},
                token: "{$token}"
            }, function () {
                var url = "{$scriptUrl}?source_order_id={$sourceOrderId}";
                window.open(url, "_blank");
            });
        });
    });
</script>
HTML;
    }
}

Vous avez essayé de retirer le constructeur? Je n’en ai jamais utilisé pour des hooks

Oui, ça ne marche pas non plus.

Lorsque j’affiche les fichiers hooks appelés, mon fichier n’y est pas :

Hook file : actions_multicompany.class.php
Hook class : ActionsMulticompany
Hook file : actions_quicklist.class.php
Hook class : ActionsQuicklist
Hook file : actions_customline.class.php
Hook class : ActionsCustomline
Hook file : actions_customline.class.php
Hook class : ActionsCustomline
Hook file : actions_multicompany.class.php
Hook class : ActionsMulticompany
Hook file : actions_multicompany.class.php
Hook class : ActionsMulticompany
Hook file : actions_infraspackplus.class.php
Hook class : ActionsInfraspackplus
Hook file : actions_multicompany.class.php
Hook class : ActionsMulticompany

J’ai copié-collé le contenu de votre fichier dans un de mes modules et le bouton est bien là, vos méthodes fonctionnent donc le problème vient probablement du fichier de configuration.

Avez-vous penser à désactiver/réactiver le module? Je sais que c’est ce qui me joue le plus souvent des tours.

C’est une bonne nouvelle que cela fonctionne chez vous (merci pour les efforts), j’ai essayé plusieurs fois d’activer et désactiver mais toujours pas, voici mon fichier modOrdersBetweenEntities :

<?php
/* Copyright (C) 2004-2018  Laurent Destailleur     <[email protected]>
 * Copyright (C) 2018-2019  Nicolas ZABOURI         <[email protected]>
 * Copyright (C) 2019-2020  Frédéric France         <[email protected]>
 * Copyright (C) 2025 SuperAdmin <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

/**
 * 	\defgroup   ordersbetweenentities     Module OrdersBetweenEntities
 *  \brief      OrdersBetweenEntities module descriptor.
 *
 *  \file       htdocs/ordersbetweenentities/core/modules/modOrdersBetweenEntities.class.php
 *  \ingroup    ordersbetweenentities
 *  \brief      Description and activation file for module OrdersBetweenEntities
 */
include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';

/**
 *  Description and activation class for module OrdersBetweenEntities
 */
class modOrdersBetweenEntities extends DolibarrModules
{
	/**
	 * Constructor. Define names, constants, directories, boxes, permissions
	 *
	 * @param DoliDB $db Database handler
	 */
	public function __construct($db)
	{
		global $langs, $conf;
		$this->db = $db;

		// Id for module (must be unique).
		// Use here a free id (See in Home -> System information -> Dolibarr for list of used modules id).
		$this->numero = 500000; // TODO Go on page https://wiki.dolibarr.org/index.php/List_of_modules_id to reserve an id number for your module

		// Key text used to identify module (for permissions, menus, etc...)
		$this->rights_class = 'ordersbetweenentities';

		// Family can be 'base' (core modules),'crm','financial','hr','projects','products','ecm','technic' (transverse modules),'interface' (link with external tools),'other','...'
		// It is used to group modules by family in module setup page
		$this->family = "other";

		// Module position in the family on 2 digits ('01', '10', '20', ...)
		$this->module_position = '90';

		// Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this)
		//$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily")));
		// Module label (no space allowed), used if translation string 'ModuleOrdersBetweenEntitiesName' not found (OrdersBetweenEntities is name of module).
		$this->name = 'ordersbetweenentities';

		// Module description, used if translation string 'ModuleOrdersBetweenEntitiesDesc' not found (OrdersBetweenEntities is name of module).
		$this->description = "OrdersBetweenEntitiesDescription";
		// Used only if file README.md and README-LL.md not found.
		$this->descriptionlong = "OrdersBetweenEntitiesDescription";

		// Author
		$this->editor_name = 'Editor name';
		$this->editor_url = 'https://www.example.com';

		// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated' or a version string like 'x.y.z'
		$this->version = '1.0';
		// Url to the file with your last numberversion of this module
		//$this->url_last_version = 'http://www.example.com/versionmodule.txt';

		// Key used in llx_const table to save module status enabled/disabled (where ORDERSBETWEENENTITIES is value of property name of module in uppercase)
		$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);

		// Name of image file used for this module.
		// If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue'
		// If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module'
		// To use a supported fa-xxx css style of font awesome, use this->picto='xxx'
		$this->picto = 'generic';

		// Define some features supported by module (triggers, login, substitutions, menus, css, etc...)
		$this->module_parts = array(
			// Set this to 1 if module has its own trigger directory (core/triggers)
			'triggers' => 0,
			// Set this to 1 if module has its own login method file (core/login)
			//'login' => 0,
			// Set this to 1 if module has its own substitution function file (core/substitutions)
			//'substitutions' => 0,
			// Set this to 1 if module has its own menus handler directory (core/menus)
			//'menus' => 0,
			// Set this to 1 if module overwrite template dir (core/tpl)
			//'tpl' => 0,
			// Set this to 1 if module has its own barcode directory (core/modules/barcode)
			//'barcode' => 0,
			// Set this to 1 if module has its own models directory (core/modules/xxx)
			//'models' => 0,
			// Set this to 1 if module has its own printing directory (core/modules/printing)
			//'printing' => 0,
			// Set this to 1 if module has its own theme directory (theme)
			//'theme' => 0,
			// Set this to relative path of css file if module has its own css file
			'css' => array(
				//    '/ordersbetweenentities/css/ordersbetweenentities.css.php',
			),
			// Set this to relative path of js file if module must load a js on all pages
			'js' => array(
				//   '/ordersbetweenentities/js/ordersbetweenentities.js.php',
			),
			// Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all'
			'hooks' => array(
				'ordersuppliercard',
				'globalcard'
			),
			// Set this to 1 if features of module are opened to external users
			'moduleforexternal' => 0,
		);

		// Data directories to create when module is enabled.
		// Example: this->dirs = array("/ordersbetweenentities/temp","/ordersbetweenentities/subdir");
		$this->dirs = array("/ordersbetweenentities/temp");

		// Config pages. Put here list of php page, stored into ordersbetweenentities/admin directory, to use to setup module.
		$this->config_page_url = array("setup.php@ordersbetweenentities");

		// Dependencies
		// A condition to hide module
		$this->hidden = false;
		// List of module class names as string that must be enabled if this module is enabled. Example: array('always1'=>'modModuleToEnable1','always2'=>'modModuleToEnable2', 'FR1'=>'modModuleToEnableFR'...)
		$this->depends = array();
		$this->requiredby = array(); // List of module class names as string to disable if this one is disabled. Example: array('modModuleToDisable1', ...)
		$this->conflictwith = array(); // List of module class names as string this module is in conflict with. Example: array('modModuleToDisable1', ...)

		// The language file dedicated to your module
		$this->langfiles = array("ordersbetweenentities@ordersbetweenentities");

		// Prerequisites
		$this->phpmin = array(5, 5); // Minimum version of PHP required by module
		$this->need_dolibarr_version = array(11, -3); // Minimum version of Dolibarr required by module

		// Messages at activation
		$this->warnings_activation = array(); // Warning to show when we activate module. array('always'='text') or array('FR'='textfr','ES'='textes'...)
		$this->warnings_activation_ext = array(); // Warning to show when we activate an external module. array('always'='text') or array('FR'='textfr','ES'='textes'...)
		//$this->automatic_activation = array('FR'=>'OrdersBetweenEntitiesWasAutomaticallyActivatedBecauseOfYourCountryChoice');
		//$this->always_enabled = true;								// If true, can't be disabled

		// Constants
		// List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive)
		// Example: $this->const=array(1 => array('ORDERSBETWEENENTITIES_MYNEWCONST1', 'chaine', 'myvalue', 'This is a constant to add', 1),
		//                             2 => array('ORDERSBETWEENENTITIES_MYNEWCONST2', 'chaine', 'myvalue', 'This is another constant to add', 0, 'current', 1)
		// );
		$this->const = array();

		// Some keys to add into the overwriting translation tables
		/*$this->overwrite_translation = array(
			'en_US:ParentCompany'=>'Parent company or reseller',
			'fr_FR:ParentCompany'=>'Maison mère ou revendeur'
		)*/

		if (!isset($conf->ordersbetweenentities) || !isset($conf->ordersbetweenentities->enabled)) {
			$conf->ordersbetweenentities = new stdClass();
			$conf->ordersbetweenentities->enabled = 0;
		}

		// Array to add new pages in new tabs
		$this->tabs = array();
		// Example:
		// $this->tabs[] = array('data'=>'objecttype:+tabname1:Title1:mylangfile@ordersbetweenentities:$user->rights->ordersbetweenentities->read:/ordersbetweenentities/mynewtab1.php?id=__ID__');  					// To add a new tab identified by code tabname1
		// $this->tabs[] = array('data'=>'objecttype:+tabname2:SUBSTITUTION_Title2:mylangfile@ordersbetweenentities:$user->rights->othermodule->read:/ordersbetweenentities/mynewtab2.php?id=__ID__',  	// To add another new tab identified by code tabname2. Label will be result of calling all substitution functions on 'Title2' key.
		// $this->tabs[] = array('data'=>'objecttype:-tabname:NU:conditiontoremove');                                                     										// To remove an existing tab identified by code tabname
		//
		// Where objecttype can be
		// 'categories_x'	  to add a tab in category view (replace 'x' by type of category (0=product, 1=supplier, 2=customer, 3=member)
		// 'contact'          to add a tab in contact view
		// 'contract'         to add a tab in contract view
		// 'group'            to add a tab in group view
		// 'intervention'     to add a tab in intervention view
		// 'invoice'          to add a tab in customer invoice view
		// 'invoice_supplier' to add a tab in supplier invoice view
		// 'member'           to add a tab in fundation member view
		// 'opensurveypoll'	  to add a tab in opensurvey poll view
		// 'order'            to add a tab in customer order view
		// 'order_supplier'   to add a tab in supplier order view
		// 'payment'		  to add a tab in payment view
		// 'payment_supplier' to add a tab in supplier payment view
		// 'product'          to add a tab in product view
		// 'propal'           to add a tab in propal view
		// 'project'          to add a tab in project view
		// 'stock'            to add a tab in stock view
		// 'thirdparty'       to add a tab in third party view
		// 'user'             to add a tab in user view

		// Dictionaries
		$this->dictionaries = array();
		/* Example:
		$this->dictionaries=array(
			'langs'=>'ordersbetweenentities@ordersbetweenentities',
			// List of tables we want to see into dictonnary editor
			'tabname'=>array(MAIN_DB_PREFIX."table1", MAIN_DB_PREFIX."table2", MAIN_DB_PREFIX."table3"),
			// Label of tables
			'tablib'=>array("Table1", "Table2", "Table3"),
			// Request to select fields
			'tabsql'=>array('SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table1 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table2 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table3 as f'),
			// Sort order
			'tabsqlsort'=>array("label ASC", "label ASC", "label ASC"),
			// List of fields (result of select to show dictionary)
			'tabfield'=>array("code,label", "code,label", "code,label"),
			// List of fields (list of fields to edit a record)
			'tabfieldvalue'=>array("code,label", "code,label", "code,label"),
			// List of fields (list of fields for insert)
			'tabfieldinsert'=>array("code,label", "code,label", "code,label"),
			// Name of columns with primary key (try to always name it 'rowid')
			'tabrowid'=>array("rowid", "rowid", "rowid"),
			// Condition to show each dictionary
			'tabcond'=>array($conf->ordersbetweenentities->enabled, $conf->ordersbetweenentities->enabled, $conf->ordersbetweenentities->enabled)
		);
		*/

		// Boxes/Widgets
		// Add here list of php file(s) stored in ordersbetweenentities/core/boxes that contains a class to show a widget.
		$this->boxes = array(
			//  0 => array(
			//      'file' => 'ordersbetweenentitieswidget1.php@ordersbetweenentities',
			//      'note' => 'Widget provided by OrdersBetweenEntities',
			//      'enabledbydefaulton' => 'Home',
			//  ),
			//  ...
		);

		// Cronjobs (List of cron jobs entries to add when module is enabled)
		// unit_frequency must be 60 for minute, 3600 for hour, 86400 for day, 604800 for week
		$this->cronjobs = array(
			//  0 => array(
			//      'label' => 'MyJob label',
			//      'jobtype' => 'method',
			//      'class' => '/ordersbetweenentities/class/myobject.class.php',
			//      'objectname' => 'MyObject',
			//      'method' => 'doScheduledJob',
			//      'parameters' => '',
			//      'comment' => 'Comment',
			//      'frequency' => 2,
			//      'unitfrequency' => 3600,
			//      'status' => 0,
			//      'test' => '$conf->ordersbetweenentities->enabled',
			//      'priority' => 50,
			//  ),
		);
		// Example: $this->cronjobs=array(
		//    0=>array('label'=>'My label', 'jobtype'=>'method', 'class'=>'/dir/class/file.class.php', 'objectname'=>'MyClass', 'method'=>'myMethod', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>2, 'unitfrequency'=>3600, 'status'=>0, 'test'=>'$conf->ordersbetweenentities->enabled', 'priority'=>50),
		//    1=>array('label'=>'My label', 'jobtype'=>'command', 'command'=>'', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>1, 'unitfrequency'=>3600*24, 'status'=>0, 'test'=>'$conf->ordersbetweenentities->enabled', 'priority'=>50)
		// );

		// Permissions provided by this module
		$this->rights = array();
		$r = 0;
		// Add here entries to declare new permissions
		/* BEGIN MODULEBUILDER PERMISSIONS */
		$this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used)
		$this->rights[$r][1] = 'Read objects of OrdersBetweenEntities'; // Permission label
		$this->rights[$r][4] = 'myobject'; // In php code, permission will be checked by test if ($user->rights->ordersbetweenentities->level1->level2)
		$this->rights[$r][5] = 'read'; // In php code, permission will be checked by test if ($user->rights->ordersbetweenentities->level1->level2)
		$r++;
		$this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used)
		$this->rights[$r][1] = 'Create/Update objects of OrdersBetweenEntities'; // Permission label
		$this->rights[$r][4] = 'myobject'; // In php code, permission will be checked by test if ($user->rights->ordersbetweenentities->level1->level2)
		$this->rights[$r][5] = 'write'; // In php code, permission will be checked by test if ($user->rights->ordersbetweenentities->level1->level2)
		$r++;
		$this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used)
		$this->rights[$r][1] = 'Delete objects of OrdersBetweenEntities'; // Permission label
		$this->rights[$r][4] = 'myobject'; // In php code, permission will be checked by test if ($user->rights->ordersbetweenentities->level1->level2)
		$this->rights[$r][5] = 'delete'; // In php code, permission will be checked by test if ($user->rights->ordersbetweenentities->level1->level2)
		$r++;
		/* END MODULEBUILDER PERMISSIONS */

		// Main menu entries to add
		//$this->menu = array();
		$r = 0;
		// Add here entries to declare new menus
		/* BEGIN MODULEBUILDER TOPMENU 
		$this->menu[$r++] = array(
			'fk_menu'=>'', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
			'type'=>'top', // This is a Top menu entry
			'titre'=>'ModuleOrdersBetweenEntitiesName',
			'mainmenu'=>'ordersbetweenentities',
			'leftmenu'=>'',
			'url'=>'/ordersbetweenentities/ordersbetweenentitiesindex.php',
			'langs'=>'ordersbetweenentities@ordersbetweenentities', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
			'position'=>1000 + $r,
			'enabled'=>'$conf->ordersbetweenentities->enabled', // Define condition to show or hide menu entry. Use '$conf->ordersbetweenentities->enabled' if entry must be visible if module is enabled.
			'perms'=>'1', // Use 'perms'=>'$user->rights->ordersbetweenentities->myobject->read' if you want your menu with a permission rules
			'target'=>'',
			'user'=>2, // 0=Menu for internal users, 1=external users, 2=both
		);
		/* END MODULEBUILDER TOPMENU */
		/* BEGIN MODULEBUILDER LEFTMENU MYOBJECT
		$this->menu[$r++]=array(
			'fk_menu'=>'fk_mainmenu=ordersbetweenentities',      // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
			'type'=>'left',                          // This is a Top menu entry
			'titre'=>'MyObject',
			'mainmenu'=>'ordersbetweenentities',
			'leftmenu'=>'myobject',
			'url'=>'/ordersbetweenentities/ordersbetweenentitiesindex.php',
			'langs'=>'ordersbetweenentities@ordersbetweenentities',	        // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
			'position'=>1000+$r,
			'enabled'=>'$conf->ordersbetweenentities->enabled',  // Define condition to show or hide menu entry. Use '$conf->ordersbetweenentities->enabled' if entry must be visible if module is enabled.
			'perms'=>'$user->rights->ordersbetweenentities->myobject->read',			                // Use 'perms'=>'$user->rights->ordersbetweenentities->level1->level2' if you want your menu with a permission rules
			'target'=>'',
			'user'=>2,				                // 0=Menu for internal users, 1=external users, 2=both
		);
		$this->menu[$r++]=array(
			'fk_menu'=>'fk_mainmenu=ordersbetweenentities,fk_leftmenu=myobject',	    // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
			'type'=>'left',			                // This is a Left menu entry
			'titre'=>'List_MyObject',
			'mainmenu'=>'ordersbetweenentities',
			'leftmenu'=>'ordersbetweenentities_myobject_list',
			'url'=>'/ordersbetweenentities/myobject_list.php',
			'langs'=>'ordersbetweenentities@ordersbetweenentities',	        // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
			'position'=>1000+$r,
			'enabled'=>'$conf->ordersbetweenentities->enabled',  // Define condition to show or hide menu entry. Use '$conf->ordersbetweenentities->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
			'perms'=>'$user->rights->ordersbetweenentities->myobject->read',			                // Use 'perms'=>'$user->rights->ordersbetweenentities->level1->level2' if you want your menu with a permission rules
			'target'=>'',
			'user'=>2,				                // 0=Menu for internal users, 1=external users, 2=both
		);
		$this->menu[$r++]=array(
			'fk_menu'=>'fk_mainmenu=ordersbetweenentities,fk_leftmenu=myobject',	    // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
			'type'=>'left',			                // This is a Left menu entry
			'titre'=>'New_MyObject',
			'mainmenu'=>'ordersbetweenentities',
			'leftmenu'=>'ordersbetweenentities_myobject_new',
			'url'=>'/ordersbetweenentities/myobject_card.php?action=create',
			'langs'=>'ordersbetweenentities@ordersbetweenentities',	        // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
			'position'=>1000+$r,
			'enabled'=>'$conf->ordersbetweenentities->enabled',  // Define condition to show or hide menu entry. Use '$conf->ordersbetweenentities->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
			'perms'=>'$user->rights->ordersbetweenentities->myobject->write',			                // Use 'perms'=>'$user->rights->ordersbetweenentities->level1->level2' if you want your menu with a permission rules
			'target'=>'',
			'user'=>2,				                // 0=Menu for internal users, 1=external users, 2=both
		);
		END MODULEBUILDER LEFTMENU MYOBJECT */
		// Exports profiles provided by this module
		$r = 1;
		/* BEGIN MODULEBUILDER EXPORT MYOBJECT */
		/*
		$langs->load("ordersbetweenentities@ordersbetweenentities");
		$this->export_code[$r]=$this->rights_class.'_'.$r;
		$this->export_label[$r]='MyObjectLines';	// Translation key (used only if key ExportDataset_xxx_z not found)
		$this->export_icon[$r]='myobject@ordersbetweenentities';
		// Define $this->export_fields_array, $this->export_TypeFields_array and $this->export_entities_array
		$keyforclass = 'MyObject'; $keyforclassfile='/ordersbetweenentities/class/myobject.class.php'; $keyforelement='myobject@ordersbetweenentities';
		include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php';
		//$this->export_fields_array[$r]['t.fieldtoadd']='FieldToAdd'; $this->export_TypeFields_array[$r]['t.fieldtoadd']='Text';
		//unset($this->export_fields_array[$r]['t.fieldtoremove']);
		//$keyforclass = 'MyObjectLine'; $keyforclassfile='/ordersbetweenentities/class/myobject.class.php'; $keyforelement='myobjectline@ordersbetweenentities'; $keyforalias='tl';
		//include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php';
		$keyforselect='myobject'; $keyforaliasextra='extra'; $keyforelement='myobject@ordersbetweenentities';
		include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
		//$keyforselect='myobjectline'; $keyforaliasextra='extraline'; $keyforelement='myobjectline@ordersbetweenentities';
		//include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
		//$this->export_dependencies_array[$r] = array('myobjectline'=>array('tl.rowid','tl.ref')); // To force to activate one or several fields if we select some fields that need same (like to select a unique key if we ask a field of a child to avoid the DISTINCT to discard them, or for computed field than need several other fields)
		//$this->export_special_array[$r] = array('t.field'=>'...');
		//$this->export_examplevalues_array[$r] = array('t.field'=>'Example');
		//$this->export_help_array[$r] = array('t.field'=>'FieldDescHelp');
		$this->export_sql_start[$r]='SELECT DISTINCT ';
		$this->export_sql_end[$r]  =' FROM '.MAIN_DB_PREFIX.'myobject as t';
		//$this->export_sql_end[$r]  =' LEFT JOIN '.MAIN_DB_PREFIX.'myobject_line as tl ON tl.fk_myobject = t.rowid';
		$this->export_sql_end[$r] .=' WHERE 1 = 1';
		$this->export_sql_end[$r] .=' AND t.entity IN ('.getEntity('myobject').')';
		$r++; */
		/* END MODULEBUILDER EXPORT MYOBJECT */

		// Imports profiles provided by this module
		$r = 1;
		/* BEGIN MODULEBUILDER IMPORT MYOBJECT */
		/*
		 $langs->load("ordersbetweenentities@ordersbetweenentities");
		 $this->export_code[$r]=$this->rights_class.'_'.$r;
		 $this->export_label[$r]='MyObjectLines';	// Translation key (used only if key ExportDataset_xxx_z not found)
		 $this->export_icon[$r]='myobject@ordersbetweenentities';
		 $keyforclass = 'MyObject'; $keyforclassfile='/ordersbetweenentities/class/myobject.class.php'; $keyforelement='myobject@ordersbetweenentities';
		 include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php';
		 $keyforselect='myobject'; $keyforaliasextra='extra'; $keyforelement='myobject@ordersbetweenentities';
		 include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
		 //$this->export_dependencies_array[$r]=array('mysubobject'=>'ts.rowid', 't.myfield'=>array('t.myfield2','t.myfield3')); // To force to activate one or several fields if we select some fields that need same (like to select a unique key if we ask a field of a child to avoid the DISTINCT to discard them, or for computed field than need several other fields)
		 $this->export_sql_start[$r]='SELECT DISTINCT ';
		 $this->export_sql_end[$r]  =' FROM '.MAIN_DB_PREFIX.'myobject as t';
		 $this->export_sql_end[$r] .=' WHERE 1 = 1';
		 $this->export_sql_end[$r] .=' AND t.entity IN ('.getEntity('myobject').')';
		 $r++; */
		/* END MODULEBUILDER IMPORT MYOBJECT */
	}

	/**
	 *  Function called when module is enabled.
	 *  The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database.
	 *  It also creates data directories
	 *
	 *  @param      string  $options    Options when enabling module ('', 'noboxes')
	 *  @return     int             	1 if OK, 0 if KO
	 */
	public function init($options = '')
	{
		global $conf, $langs;

		$result = $this->_load_tables('/ordersbetweenentities/sql/');
		if ($result < 0) return -1; // Do not activate module if error 'not allowed' returned when loading module SQL queries (the _load_table run sql with run_sql with the error allowed parameter set to 'default')

		// Create extrafields during init
		//include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
		//$extrafields = new ExtraFields($this->db);
		//$result1=$extrafields->addExtraField('ordersbetweenentities_myattr1', "New Attr 1 label", 'boolean', 1,  3, 'thirdparty',   0, 0, '', '', 1, '', 0, 0, '', '', 'ordersbetweenentities@ordersbetweenentities', '$conf->ordersbetweenentities->enabled');
		//$result2=$extrafields->addExtraField('ordersbetweenentities_myattr2', "New Attr 2 label", 'varchar', 1, 10, 'project',      0, 0, '', '', 1, '', 0, 0, '', '', 'ordersbetweenentities@ordersbetweenentities', '$conf->ordersbetweenentities->enabled');
		//$result3=$extrafields->addExtraField('ordersbetweenentities_myattr3', "New Attr 3 label", 'varchar', 1, 10, 'bank_account', 0, 0, '', '', 1, '', 0, 0, '', '', 'ordersbetweenentities@ordersbetweenentities', '$conf->ordersbetweenentities->enabled');
		//$result4=$extrafields->addExtraField('ordersbetweenentities_myattr4', "New Attr 4 label", 'select',  1,  3, 'thirdparty',   0, 1, '', array('options'=>array('code1'=>'Val1','code2'=>'Val2','code3'=>'Val3')), 1,'', 0, 0, '', '', 'ordersbetweenentities@ordersbetweenentities', '$conf->ordersbetweenentities->enabled');
		//$result5=$extrafields->addExtraField('ordersbetweenentities_myattr5', "New Attr 5 label", 'text',    1, 10, 'user',         0, 0, '', '', 1, '', 0, 0, '', '', 'ordersbetweenentities@ordersbetweenentities', '$conf->ordersbetweenentities->enabled');

		// Permissions
		$this->remove($options);

		$sql = array();

		// Document templates
		$moduledir = 'ordersbetweenentities';
		$myTmpObjects = array();
		$myTmpObjects['MyObject'] = array('includerefgeneration'=>0, 'includedocgeneration'=>0);

		foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) {
			if ($myTmpObjectKey == 'MyObject') continue;
			if ($myTmpObjectArray['includerefgeneration']) {
				$src = DOL_DOCUMENT_ROOT.'/install/doctemplates/ordersbetweenentities/template_myobjects.odt';
				$dirodt = DOL_DATA_ROOT.'/doctemplates/ordersbetweenentities';
				$dest = $dirodt.'/template_myobjects.odt';

				if (file_exists($src) && !file_exists($dest))
				{
					require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
					dol_mkdir($dirodt);
					$result = dol_copy($src, $dest, 0, 0);
					if ($result < 0)
					{
						$langs->load("errors");
						$this->error = $langs->trans('ErrorFailToCopyFile', $src, $dest);
						return 0;
					}
				}

				$sql = array_merge($sql, array(
					"DELETE FROM ".MAIN_DB_PREFIX."document_model WHERE nom = 'standard_".strtolower($myTmpObjectKey)."' AND type = '".strtolower($myTmpObjectKey)."' AND entity = ".$conf->entity,
					"INSERT INTO ".MAIN_DB_PREFIX."document_model (nom, type, entity) VALUES('standard_".strtolower($myTmpObjectKey)."','".strtolower($myTmpObjectKey)."',".$conf->entity.")",
					"DELETE FROM ".MAIN_DB_PREFIX."document_model WHERE nom = 'generic_".strtolower($myTmpObjectKey)."_odt' AND type = '".strtolower($myTmpObjectKey)."' AND entity = ".$conf->entity,
					"INSERT INTO ".MAIN_DB_PREFIX."document_model (nom, type, entity) VALUES('generic_".strtolower($myTmpObjectKey)."_odt', '".strtolower($myTmpObjectKey)."', ".$conf->entity.")"
				));
			}
		}

		return $this->_init($sql, $options);
	}

	/**
	 *  Function called when module is disabled.
	 *  Remove from database constants, boxes and permissions from Dolibarr database.
	 *  Data directories are not deleted
	 *
	 *  @param      string	$options    Options when enabling module ('', 'noboxes')
	 *  @return     int                 1 if OK, 0 if KO
	 */
	public function remove($options = '')
	{
		$sql = array();
		return $this->_remove($sql, $options);
	}
}

Bonjour

La version 13 est plutôt ancienne, votre problème vient peut-être de là.
Vous pourriez tester votre module sur une version plus récente de Dolibarr

Oui, apparemment cela fonctionne bien sur la version 19, comme le montre @ArthurL, mais il se peut que ce soit ma démarche ou un extrait de mon code dans le fichier de configuration du module qui empêche le bon fonctionnement chez moi.

J’ai oublié de préciser que ce module fonctionne bien sur l’un de mes clones Dolibarr, de la même version (13.0.2), mais hébergé ailleurs. J’ai suivi la même démarche de création, mais sur mon Dolibarr de production, cela ne veut pas fonctionner.

Je vois que vous avez laissé le numéro du module à 500000, peut-être que deux modules partagent ce numéro et cela l’empêche de fonctionner.

Le module est exactement le même que sur votre clone? Car s’il fonctionne sur votre version de test, c’est étrange qu’il ne fonctionne pas en prod

Oui, je n’avais pas changé le numéro du module. Je viens de le faire au cas où un autre module partagerait le même numéro, mais cela ne résout pas le problème.

C’est très étrange, car je suis exactement les mêmes étapes : création du module, modification du fichier mod..., création du fichier actions avec le même code dans /custom/ordersbetweenentities/class que celui de test, et pourtant ça fonctionne en test mais pas en prod.

et un petit lien vers ma vidéo explicative des hook/trigger et autre joyeuseté : https://www.youtube.com/watch?v=pyc6ueNLywc

Merci pour la parenthèse. J’ai regardé chaque seconde de votre vidéo, mais il doit y avoir quelque chose que je fais mal pour que ça ne fonctionne pas.

@ArthurL, peux-tu me donner les étapes au détaille prêt pour que mon code fonctionne dans ton Dolibarr ?

avez-vous la possibilité de réaliser un test sur un dolibarr plus récent?
Je suis allée regardé sur une ancienne version d’un de mes modules et voila comment était défini les hook alors :
$this->module_parts = array(
‹ hooks › => array(‹ agenda ›,‹ contacttpl ›,‹ propalcard ›,‹ interventioncard ›), // used for agenda display
‹ models › => 1,
‹ triggers › => 1);

Autre remarque, si vous etes avec du multientity, il est probable que vous deviez activer votre module sur chaque instance…

J’ai juste copier coller vos méthodes dans un fichier qui fonctionne. Je peux envoyer le github du module concerné si vous le souhaitez pour voir comment le module est construit

oui, mon code a bien fonctionné pour @ArthurL dans sa version de dolibarr (19), mais ce qu’est étrange c’est que j’ai réussi à faire fonctionner ce module dans mon dolibarr de test mais pas dans celui de production.

J’ai également fait attention à ce que le module soit active dans les deux entités.

Pas besoin, j’ai plein de fichiers qui fonctionnent. Justement, je pense que c’est dans le processus de création de ce fichier qu’il y a quelque chose qui cloche, car Dolibarr n’arrive pas à lire le hook ordersuppliercard que j’ajoute dans le module_parts de mon module.