Les extrafields Dolibarr permettent d'ajouter des champs personnalisés aux objets métier. SmartMaker offre un système complet pour les exposer à l'application React avec génération automatique de formulaires.
Le système extrafields SmartMaker fonctionne en trois étapes :
1. Admin Dolibarr : Configure quels extrafields exposer (RO/RW)
↓
2. Mapper PHP : Lit la configuration, expose les métadonnées
↓
3. React : Génère dynamiquement les formulaires
SmartBoot génère une page d'administration admin/smartmaker.php qui permet de configurer les extrafields exposés à l'application.
// Définir les objets Dolibarr à configurer $elementsToConfig = array( 'fichinter' => 'Interventions', 'societe' => 'Tiers', 'projet_task' => 'Tâches', );
Cette page affiche tous les extrafields de chaque objet avec deux colonnes :
| Option | Description |
|---|---|
| RO (Read-Only) | Le champ est visible dans l'application mais non modifiable |
| RW (Read-Write) | Le champ est visible ET modifiable |
La configuration est stockée dans les constantes Dolibarr :
MONMODULE_SMARTMAKER_EXTRAFIELDS_RO = "champ1,champ2,champ3" MONMODULE_SMARTMAKER_EXTRAFIELDS_RW = "champ4,champ5"
Le mapper hérite de dmBase et utilise dmTrait pour la gestion des extrafields :
<?php namespace MonModule\Api; use SmartAuth\DolibarrMapping\dmBase; use SmartAuth\DolibarrMapping\dmTrait; class dmItem extends dmBase { use dmTrait; // Classe Dolibarr source protected $parentClassName = 'Fichinter'; // Element pour les extrafields (table llx_extrafields) protected $parentElementToUseForExtraFields = 'fichinter'; // Mapping des champs standards protected $listOfPublishedFields = [ 'rowid' => 'id', 'ref' => 'ref', 'description' => 'description', 'fk_statut' => 'status', ]; // Champs modifiables via API protected $writableFields = []; }
Le constructeur charge la configuration depuis Dolibarr :
public function __construct() { global $db; $this->db = $db; // Extrafields en lecture seule $extRO = getDolGlobalString('MONMODULE_SMARTMAKER_EXTRAFIELDS_RO'); if (!empty($extRO)) { foreach (explode(',', $extRO) as $field) { $field = trim($field); if (!empty($field)) { $key = 'options_' . $field; $this->listOfPublishedFields[$key] = $key; } } } // Extrafields en lecture-écriture $extRW = getDolGlobalString('MONMODULE_SMARTMAKER_EXTRAFIELDS_RW'); if (!empty($extRW)) { foreach (explode(',', $extRW) as $field) { $field = trim($field); if (!empty($field)) { $key = 'options_' . $field; $this->listOfPublishedFields[$key] = $key; $this->writableFields[] = $key; } } } $this->boot(); }
La méthode objectDesc() (fournie par dmTrait) retourne les métadonnées des champs pour le frontend :
// Dans le controller $mapping = new dmItem(); $config = $mapping->objectDesc(); // Retourne un tableau avec les infos de chaque champ : // [ // 'options_niveau' => [ // 'type' => 'select', // 'label' => 'Niveau', // 'required' => true, // 'options' => ['1' => 'Facile', '2' => 'Moyen', '3' => 'Difficile'], // 'writable' => true, // ], // ... // ]
Le controller doit retourner la configuration au frontend :
class ItemController { private $mapping; public function __construct() { $this->mapping = new dmItem(); } // GET /items (liste + config) public function index($arr = null) { // ... charger les items ... return [[ 'statusCode' => 200, 'items' => $items, 'config' => $this->mapping->objectDesc(), // Métadonnées des champs ], 200]; } // GET /items/123 (détail + config) public function show($arr = null) { $id = $arr['id'] ?? 0; // ... charger l'item ... return [[ 'statusCode' => 200, 'item' => $this->mapping->map($dolibarrObject), 'config' => $this->mapping->objectDesc(), ], 200]; } }
Stocker la configuration reçue de l'API dans Redux ou le state local :
// Dans le composant ou via Redux
const [config, setConfig] = useState({});
useEffect(() => {
api.private.get('items')
.then(response => {
setConfig(response.config || {});
// ... traiter les items ...
});
}, []);
SmartCommon fournit FormComponentsMap qui mappe les types Dolibarr vers les composants React :
import { FormComponentsMap } from "@cap-rel/smartcommon";
// FormComponentsMap retourne le composant approprié selon le type :
// 'varchar' → Input
// 'text' → Textarea
// 'int' → Input type="number"
// 'double' → Input type="number" step="0.01"
// 'date' → DatePicker
// 'datetime' → DateTimePicker
// 'boolean' → Checkbox
// 'select' → Select
// 'sellist' → Select (avec options depuis la DB)
// 'radio' → RadioGroup
// 'checkbox' → CheckboxGroup
// 'link' → SearchSelect (lien vers autre objet)
const DynamicForm = ({ config, values, onChange }) => {
return (
<div className="flex flex-col gap-4">
{Object.entries(config).map(([fieldName, fieldConfig]) => {
// Récupérer le composant selon le type
const Component = FormComponentsMap(fieldConfig.type);
if (!Component) return null;
return (
<Component
key={fieldName}
label={fieldConfig.label}
value={values[fieldName] || ''}
onChange={(val) => onChange(fieldName, val)}
required={fieldConfig.required}
disabled={!fieldConfig.writable}
options={fieldConfig.options} // Pour select/radio
/>
);
})}
</div>
);
};
import { useStates, useApi, Page, Button, Block } from "@cap-rel/smartcommon";
import { FormComponentsMap } from "@cap-rel/smartcommon";
export const ItemEditPage = ({ itemId }) => {
const { states, set } = useStates({
item: null,
config: {},
isLoading: true,
isSaving: false,
});
const { item, config, isLoading, isSaving } = states;
const api = useApi();
// Charger l'item et sa configuration
useEffect(() => {
api.private.get(`items/${itemId}`)
.then(response => {
set("item", response.item);
set("config", response.config);
})
.finally(() => set("isLoading", false));
}, [itemId]);
// Modifier une valeur
const handleFieldChange = (fieldName, value) => {
set("item", { ...item, [fieldName]: value });
};
// Sauvegarder
const handleSave = () => {
set("isSaving", true);
api.private.put(`items/${itemId}`, item)
.then(() => {
// Succès
})
.finally(() => set("isSaving", false));
};
if (isLoading) return <div>Chargement...</div>;
return (
<Page>
<Block title="Modifier l'item">
{/* Champs standards */}
<Input
label="Référence"
value={item?.ref || ''}
disabled
/>
{/* Extrafields dynamiques */}
{Object.entries(config).map(([fieldName, fieldConfig]) => {
// Ne montrer que les extrafields (commencent par options_)
if (!fieldName.startsWith('options_')) return null;
const Component = FormComponentsMap(fieldConfig.type);
if (!Component) return null;
return (
<Component
key={fieldName}
label={fieldConfig.label}
value={item?.[fieldName] || ''}
onChange={(val) => handleFieldChange(fieldName, val)}
disabled={!fieldConfig.writable}
required={fieldConfig.required}
options={fieldConfig.options}
/>
);
})}
<Button
onClick={handleSave}
loading={isSaving}
>
Enregistrer
</Button>
</Block>
</Page>
);
};
| Type Dolibarr | Composant React | Notes |
|---|---|---|
| varchar | Input | Texte court |
| text | Textarea | Texte long |
| int | Input (number) | Entier |
| double | Input (number) | Décimal avec step |
| date | DatePicker | Date seule |
| datetime | DateTimePicker | Date et heure |
| boolean | Checkbox | Oui/Non |
| select | Select | Liste déroulante |
| sellist | Select | Liste depuis SQL |
| radio | RadioGroup | Boutons radio |
| checkbox | CheckboxGroup | Cases à cocher multiples |
| link | SearchSelect | Lien vers objet Dolibarr |
| price | Input (number) | Prix avec formatage |
Le système extrafields SmartMaker permet de :
Ce système évite de modifier le code à chaque ajout d'extrafield : la configuration admin suffit.