Source: https://raw.githubusercontent.com/Evarisk/Saturne/develop/CLAUDE.md
Digirisk est un module Dolibarr ERP/CRM permettant de gérer les risques de votre entreprise et de créer votre Document Unique en toute simplicité. Il est basé sur le framework Saturne.
Saturne follows a strict separation of concerns:
- PHP controllers prepare data only
- TPL files render HTML only
- Business logic belongs to
class/ - Reusable UI belongs to
core/tpl/ - JavaScript is modular and event-driven
- Styling is component-scoped through
.mod-{module}
The framework prioritizes:
- maintainability over cleverness
- consistency over flexibility
- backward compatibility with Dolibarr
- reusable patterns shared across all modules
When generating code:
- Always reuse existing Saturne patterns before creating new ones
- Prefer extending generic Saturne components over module-specific implementations
- Keep generated diffs minimal
- Preserve existing comments and spacing
- Never reformat unrelated code
- Follow the surrounding file style first
- When uncertain, mimic the nearest reference file
htdocs/custom/digiriskdolibarr/
├── admin/ # Admin config pages
├── class/ # PHP CRUD classes (SaturneObject, ActionsSaturne, …)
├── core/
│ ├── ajax/ # AJAX endpoints
│ ├── tpl/ # Reusable TPL fragments (banner_actions, medias, …)
│ └── triggers/ # Dolibarr event triggers
├── css/scss/ # SCSS source → compiled to css/saturne.min.css
├── js/modules/ # JS feature modules → compiled to js/saturne.min.js
├── lib/ # snake_case PHP utility functions
├── view/ # Generic views (saturne_list.php, saturne_document.php, …)
└── gulpfile.js # Build config (child modules reference gulpfile-shared.js)
Child module entry point (digiriskdolibarr.main.inc.php):
$moduleName = 'Digirisk';
$moduleNameLowerCase = strtolower($moduleName);
require_once __DIR__ . '/../saturne/saturne.main.inc.php';Class inheritance:
class DigiriskObject extends SaturneObject {
public function __construct(DoliDB $db) {
parent::__construct($db, 'digiriskdolibarr', 'digirisk_object');
}
}Never:
- Put SQL in views
- Put HTML in classes
- Put business logic in TPL
- Use inline CSS or JS
- Call Dolibarr globals directly inside JS
- Duplicate generic components already existing in
saturne/ - Create module-specific patterns when a shared Saturne pattern exists
PHP Rendering
// BAD
echo '<script>alert()</script>';
// GOOD
saturne_header();JavaScript Event Binding
// BAD
$('.btn').click(function(){});
// GOOD
$(document).on('click', '.btn', handler);Style — follow PSR-12 for all PHP code (indentation, spacing, naming, braces, etc.).
PSR-12 is enforced via PHPCS — run phpcs --standard=PSR12. Config in .phpcs.xml at the module root.
Comments — place comments on the line above the code they document, never inline after it. Remove obvious comments that only restate what the code does — a good comment explains why, not what.
Blank lines — keep blank lines between logical code sections; they are intentional and improve readability.
Asset loading — never use <link> or <script> manually:
saturne_header(); // auto-loads saturne.min.css + saturne.min.js
// also loads digiriskdolibarr.min.css and digiriskdolibarr.min.js if they existVariables before templates — all logic runs before require_once of TPL:
$title = $langs->trans('MyPage');
$object = new MyObject($db);
$object->fetch($id);
saturne_header(0, '', $title, $help_url);
require_once __DIR__ . '/../../saturne/core/tpl/banner_actions.tpl.php';Security rules — always:
$id = GETPOSTINT('id'); // never $_GET / $_POST
$label = GETPOST('label', 'alpha');
$name = dol_sanitize_filename($name);
$html = dol_escape_htmltag($value);
$user->hasRight('digiriskdolibarr', 'write'); // check before any action
$db->escape($value); // escape SQL valuesNever:
$langs->load()manually — saturne loads lang files automatically- Raw SQL queries — use
fetch(),fetchAll(), Dolibarr ORM methods - Modify any file outside
htdocs/custom/{module}/
Hooks — action class returns 0 (continue) or 1 (replace):
class ActionsDigirisk {
public function printMainArea(array $parameters): int {
if (strpos($parameters['context'], 'digiriskdolibarrcontext') !== false) {
// custom output
}
return 0;
}
}Reference files:
- PHP action:
class/actions_saturne.class.php - TPL fragment:
core/tpl/actions/banner_actions.tpl.php - Generic view:
view/saturne_list.php
Namespace pattern — literal object, no IIFE:
window.saturne.modal = {};
window.saturne.modal.init = function() {
window.saturne.modal.event();
};
window.saturne.modal.event = function() {
$(document).on('click', '.modal-open', window.saturne.modal.openModal);
};
window.saturne.modal.openModal = function(event) { /* … */ };Rules:
- Always implement
init(),event(), and handler methods saturne.jscallsinit()on everywindow.saturne.*automatically via$(document).ready— never callinit()manually at end of file- Use jQuery — no Vanilla JS unless jQuery is unavailable
- No inline JS in TPL files
Linting — JSHint validates all JS files. Config in .jshintrc at the module root.
Reference files: js/modules/modal.js, js/modules/button.js, js/modules/object.js
Structure:
css/scss/style.scss ← entry point, imports all partials
css/scss/variable/_colors.scss
css/scss/modules/button/_button.scss
css/scss/page/_mypage.scss
Rules:
- Legacy Saturne partials may still use
@import. - New code should use
@use/@forward. - Do not refactor existing imports unless touching the file.
- Target Dolibarr overrides via the
mod-{element}class injected bysaturne_headeron<body>:.mod-digiriskdolibarr .fichecenter { /* override, not a global selector */ }
- Partials named
_{name}.scss, aggregated through_{category}.scss
Reference files: css/scss/style.scss, css/scss/variable/_colors.scss, css/scss/modules/modal/_modal.scss
- Avoid N+1 fetch loops
- Prefer bulk fetch methods
- Never load unused JS modules
- AJAX endpoints must return minimal payloads
- Large lists must support pagination
- Avoid synchronous AJAX
- Preserve existing hooks and public method signatures
- Avoid breaking database schema changes
- New constants must have migration safety
- UI changes must remain compatible with existing themes
Windows CMD :
set MODULE_NAME=saturne && node node_modules/gulp/bin/gulp.js --gulpfile gulpfile-shared.jsLinux / macOS / PowerShell :
npm start # équivalent, défini dans package.json via cross-envTâches disponibles :
| Commande | Description |
|---|---|
default |
Compile SCSS + JS puis lance le watch (dev) |
build |
Compilation prod one-shot, minifiée, sans sourcemaps |
scss_core |
SCSS dev uniquement (sourcemaps + minification) |
js_backend |
JS concat + uglify uniquement |
| Fichier | Local | CI/prod |
|---|---|---|
css/saturne.min.css |
✅ gulp (avec sourcemaps) | ✅ gulp build (sans sourcemaps) |
js/saturne.min.js |
✅ gulp | ✅ gulp build |
css/saturne.min.css.map |
✅ gulp (debug local) | ❌ jamais généré ni commité |
- La CI (
build-assets.yml) compile et commite.min.css/.min.jsautomatiquement sur push versmainoudevelop— ne jamais les commiter manuellement - Le sourcemap
.mapest dans.gitignore— uniquement utile en local pour le debug DevTools - En local, Git ignore les modifications sur les
.mingrâce àassume-unchanged:git update-index --assume-unchanged css/saturne.min.css git update-index --assume-unchanged js/saturne.min.js
- Use
npm ciin CI (reproducible installs from lock file),npm installlocally
| Tool | Purpose | Config file | Enforced in CI |
|---|---|---|---|
| PHPCS | PHP style enforcement (PSR-12) | .phpcs.xml |
✓ (blocks build) |
| phpcbf | Auto-fix PSR-12 violations | .phpcs.xml |
— (local only) |
| JSHint | JS validation | .jshintrc |
✓ (blocks build) |
| PHPStan | Static analysis — max level | phpstan.neon |
✓ (quality job) |
| Phan | Deep static analysis | .phan/config.php |
✓ (quality job) |
| PHPUnit | Unit tests | tests/phpunit/phpunittest.xml |
✓ (quality job) |
| EditorConfig | Indentation, charset, line endings consistent across all editors | .editorconfig |
— (editor-side) |
PHPCS and JSHint run before compilation in CI (build-assets-reusable.yml — lint job must pass before build job starts).
PHPStan, Phan, and PHPUnit run in a separate quality.yml workflow, triggered on push/PR to main and develop.
Indentation — this project uses spaces (PSR-12, 4 spaces), unlike Dolibarr core which uses tabs. Never mix the two.
Run locally:
# PHPCS — check
~/.composer/vendor/bin/phpcs --standard=.phpcs.xml --extensions=php --ignore=vendor,node_modules,css,js .
# phpcbf — auto-fix (run before committing)
~/.composer/vendor/bin/phpcbf --standard=.phpcs.xml .
# JSHint
jshint js/modules/*.js
# PHPStan (0 errors when baseline is current)
vendor/bin/phpstan analyse --memory-limit=512M
# PHPUnit
vendor/bin/phpunit --configuration tests/phpunit/phpunittest.xml --testdox
# Phan — requires php-ast; runs in CI (PHP 8.1) only
# vendor/bin/phan --config-file=.phan/config.phpPHPStan baseline — phpstan.baseline.neon suppresses pre-existing errors.
When you fix a baselined error, regenerate it:
vendor/bin/phpstan analyse --memory-limit=512M --generate-baseline phpstan.baseline.neonPHPUnit bootstrap — tests/phpunit/bootstrap.php is stub-only (no Dolibarr DB).
Tests that load saturne_functions.lib.php require DOL_DOCUMENT_ROOT to point to a Dolibarr htdocs/ directory (available locally and in CI via sparse checkout).
EditorConfig is picked up automatically by most editors (VSCode, PhpStorm, etc.) — install the plugin if prompted.
Branch: {type}/{issue-number}-{short-description}
→ fix/503-mail-eventpro, feat/478-menu-reorder
Never commit directly to main or develop. Dev branch: develop. PR required with ≥1 reviewer.
Never commit anything until the user has explicitly validated the changes locally. Always wait for user confirmation before running any git commit or git push command.
One issue = one branch = one PR. Never mix multiple issues in a single branch or PR.
Commit format: #{issue} [{Scope}] {type}: {short description}
| Type | Usage |
|---|---|
feat / add |
New feature |
fix |
Bug fix |
rework |
Refactor/rework |
chore / ci |
Build, CI, config |
docs / style |
Docs, formatting |
Scope: business element if broad (Projet, EventPro), technical category if focused (JS, SCSS, CI).
#503 [EventPro] fix: returnurl construction before tpl include
#478 [Menu] rework: reorder left menu entries
#1305 [JS] add: counter for all maxlength fields
Issue labels:
- Story points — add a Fibonacci label to every issue:
0,1,2,3,5,8,13,21 - PWA — add the
PWAlabel to issues related to the Progressive Web App feature
These files are the most complete and representative examples in the codebase. Use them as templates when creating new files of the same type.
class/actions_saturne.class.php (513 lines)
The canonical example of a Dolibarr hook class. Study this file to understand:
- How to structure the hook class:
printMainArea,addHtmlHeader,llxHeader,printCommonFooter,doActions,emailElementlist,getElementProperties - Return convention:
return 0to let Dolibarr continue processing,return 1to replace it - How to accumulate HTML output into
$this->resprintsbefore returning - How to return structured data via
$this->results(used for array-type hook responses) - How to guard each hook method with a context check (
strpos($parameters['context'], '...'))
// Skeleton pattern from actions_saturne.class.php
public function printMainArea(array $parameters): int {
if (strpos($parameters['context'], 'digiriskdolibarrcontext') !== false) {
$this->resprints = '<div>…HTML output…</div>';
return 1; // replace default rendering
}
return 0; // continue
}view/saturne_list.php (247 lines)
The reference implementation of a generic list view. Follow this exact sequence:
saturne_check_access()— security gate (permissions, module enabled)saturne_get_objects_metadata()— loads object definitionsGETPOSTINT()/GETPOST()— read request parameters (never$_GET/$_POST)$user->hasRight()— check write permission before any actionsaturne_header(0, '', $title, $help_url)— renders<html>+ loads CSS/JS- Sequential
require_onceof TPL fragments (list_build, list_header, list_search, list_loop, list_footer)
This file also shows how $hookmanager->executeHooks() is called in a view context, and how the body class (mod-{module}) is injected by saturne_header for SCSS scoping.
core/tpl/admin/object/object_const_view.tpl.php
Best example of an admin-facing TPL. Demonstrates:
- The mandatory comment block at the top listing all expected global variables (
$object,$user,$langs, …) $hookmanager->executeHooks('saturneAdminObjectConst', …)— the standard hook call inside a TPLajax_constantonoff()helper for toggle switches on admin config pages- How to use
$conf->global->MODULE_CONSTto read/write module config values
core/tpl/actions/banner_actions.tpl.php
Minimal but canonical TPL. Use it to understand:
- The expected comment-header format declaring every global variable the TPL relies on
- How variables must be fully prepared in the calling
.phpfile before the TPL is included (zero business logic inside a TPL) - The file naming convention:
{category}/{subcategory}/snake_case_name.tpl.php
js/modules/object.js (155 lines)
The most complete JS module in the codebase. Use it as the gold standard for:
- The three-method skeleton:
init()→event()→ named handler functions - Delegated event binding:
$(document).on('click', '.selector', handler)(never direct.click()) getFields()— how to collect form field values into a data object$.ajax({url, data, success})pattern with a named success callback (reloadListSuccess)ObjectFromModal()— how to open a modal and react to its result- jQuery-only rule: no
document.querySelector, noaddEventListener
window.saturne.object = {};
window.saturne.object.init = function() {
window.saturne.object.event();
};
window.saturne.object.event = function() {
$(document).on('click', '.object-save', window.saturne.object.save);
};
window.saturne.object.save = function(event) {
var fields = window.saturne.object.getFields();
// $.ajax(…)
};saturne.js auto-calls window.saturne.object.init() on $(document).ready — never add an init() call at the bottom of the file.
js/modules/modal.js (168 lines)
Complements object.js by showing the event-only pattern (no AJAX). Study it for:
- Reading configuration from
data-*attributes on a.modal-optionselement:$('.modal-options').data('url'),$('.modal-options').data('type') openModal/closeModal— how to toggle CSS classes and manage themodal-activestaterefreshModal— reloading modal content dynamicallyloadLazyImages()— deferred image loading triggered on modal open
css/scss/modules/modal/_modal.scss (146 lines)
Reference for a full component partial:
- BEM-like nesting with
&for element and modifier:.wpeo-modal { &.modal-active { … } .modal-container { … } } - State modifier class pattern:
.modal-activeadded/removed by JS to trigger CSS transitions - Media query with the
$media__smallvariable from_sizes.scss - Color variables from
_colors.scss($color__primary,$color__white, etc.) - Imports a sub-partial at the bottom:
@import "modal-flex"— one partial per layout concern
css/scss/modules/button/_button.scss (206 lines)
Shows the modifier-class architecture used across all Saturne components:
- Base class
.wpeo-buttonwith default styles - Modifier classes:
.button-blue,.button-grey,.button-red,.button-pill,.button-square, etc. - Color imports via
@import "colors"at the top - Sub-partial import at the bottom:
@import "button-add"for the FAB/add-button variant - How Dolibarr-specific overrides are scoped inside
.mod-{module}to avoid polluting global styles
- Zero files outside
htdocs/custom/{module}/— never touch Dolibarr core - Don't copy
gulpfile.jsinto each module — usegulpfile-shared.js - Test install/uninstall on a clean Dolibarr instance before opening a PR
.minfiles are auto-generated — conflicts on them = recompile, don't hand-merge$moduleNameLowerCasemust be set beforesaturne.main.inc.phpis required
See docs/MEMO_RELEASE.md for the full release workflow.
Short prompt to generate release notes:
claude "Generate release notes for version X.X.X based on git log since tag X.X.X. Use RELEASE_NOTES_TEMPLATE.md as format reference. Write in French, group by functional category, add screenshot placeholders for visual features. Save to RELEASE_NOTES.md"