Implement UI enhancements for participant import without transaction in MSIN-4482. Add new styles for import dropzone and integrate import functionality into event management panel. Update navigation for improved access to import features. Increment version code.

This commit is contained in:
2026-07-24 15:39:50 -04:00
parent db7b2e9f46
commit 1ca75a4ed4
6 changed files with 1358 additions and 38 deletions

View File

@ -11,26 +11,26 @@ Doc de cadrage — source de vérité pour le développement de ce chantier.
## Intention
Utiliser la suite MS1 (gestion, ChronoTrack, hub allégé, signature de dossier, accès par tâche) pour des clients **sans** tunnel dinscription / paiement en ligne MS1.
Les participants arrivent par **import fichier** et/ou **saisie rapide** ; ensuite sync ChronoTrack.
Utiliser la suite MS1 (gestion, ChronoTrack, outils promoteur existants) pour des clients **sans** tunnel dinscription / paiement en ligne MS1.
Les participants arrivent surtout par **import fichier** (puis saisie rapide plus tard) ; ensuite sync ChronoTrack.
Aussi utile le **jour J** sur une course **avec** inscription en ligne : ajout / correction rapide (sexe, âge, etc.) **sans** transaction financière.
Aussi utile plus tard le **jour J** sur une course **avec** inscription en ligne : correction / ajout rapide **sans** transaction financière.
## Décisions actées
| Sujet | Décision |
|---|---|
| Source terrain | Toujours **`resultats_*`** (`resultats_participants`, `resultats_epreuves_commandees`, `resultats_questions`) — pas le panier |
| Source terrain | Toujours **`resultats_*`** — **pas de panier / `no_panier`** pour ces lignes |
| Type événement | Nouveau type / mode « sans inscription en ligne » (ex. timing) — à créer |
| Paiement MS1 | Hors scope pour ces lignes ; **interdit** le « panier à 0 $ » |
| Clé unique | ID du fichier client = **`external_id` ChronoTrack** au push (pas seulement `par_id_original`) ; même fichier = fallback import CT |
| Questions import | **Option C** : matcher les questions existantes ; non matchées → preview + **création confirmée** |
| Hub | **Mini-hub / hub allégé** (pas zéro hub) ; grossit avec les options |
| Opérateur | MS1 (Stéphane / équipe) = promoteur opérateur ; délégation = **kits limités par tâche**, pas kit promoteur client « total » |
| Signature de dossier | **Dans le scope hub** — à cartographier (module exact) ; **≠ waiver** |
| Waiver | **Hors scope** — ne pas assimiler « signature de dossier » au waiver |
| Édition fiches | Create/edit sur `resultats_participants` (+ questions) ; **même** chemin jour J sur événement en ligne |
| Finances | Marqueur d**origine** obligatoire ; import / saisie rapide **exclus** des rapports paiement / facture |
| Paiement MS1 | Hors scope ; **interdit** le « panier à 0 $ » |
| Clé unique | ID du fichier client = **`external_id` ChronoTrack** au push ; même fichier = fallback import CT |
| Questions import | **Option C** : matcher ; non matchées → preview + **création confirmée** |
| Hub promoteur | **Ne pas toucher pour linstant** — hub actuel tel quel |
| Qui est promoteur | Opérateur MS1 se met **lui-même** promoteur ; droits clients = kits **minimisés via lexistant** |
| Mini-hub | **Plus tard**, au fur et à mesure — pas un livrable immédiat |
| Waiver | **Hors scope** |
| Édition / saisie rapide | Après limport (phase suivante) |
| Finances | Marqueur d**origine** ; import / saisie rapide exclus des rapports paiement |
## Origines participant (marqueur)
@ -38,36 +38,60 @@ Aussi utile le **jour J** sur une course **avec** inscription en ligne : ajout /
|---|---|
| `inscription_en_ligne` | Flux payant actuel |
| `import_fichier` | Bulk modèle fichier |
| `saisie_rapide` | Création unitaire / jour J |
| `saisie_rapide` | Création unitaire / jour J (plus tard) |
## Ancrages code existants
## Phases (réalignées)
- Sync CT : `php/chronotrack_api/` — push MS1→CT ; aujourdhui `external_id` ≈ `par_id_original` → **à adapter** si clé import présente
- `ct_entry_id` : ID interne CT post-sync (MSIN-4328) — distinct de la clé fichier
- Questions → CT : `entry_note_*` (MSIN-4461) via `resultats_questions` + `inscriptions_questions`
- Hub / accès : `php/inc_fx_promoteur_hub.php`, `inscriptions_eve_acces*`
- Pas dimport générique participants aujourdhui (seulement FNQ/TQ/PayPal spécialisés)
1. **UI import superadm** (en cours) — dropzone, mapping colonnes + épreuves, templates persistants
2. **Écriture `resultats_*`** — upsert clé unique + questions C (sans panier)
3. **ChronoTrack** — push avec `par_external_id`
4. **Saisie / édition** — fiche rapide (+ jour J)
5. **Hub minimisé** — seulement si/quand nécessaire
## Hors scope
## UI import (décisions)
- Waiver / `waiver_cleared` comme livrable de ce projet
- Kit promoteur client complet sur ce type dévénement
- Facturation / remboursements pour lignes import / saisie rapide
- Import inverse ChronoTrack → MS1 comme chemin nominal
- Emplacement v1 : **superadm fiche événement** (panneau sous ChronoTrack ; ancre « Import »)
- Colonne fichier pour lépreuve → match vers `epr_id` de lévénement
- UX : dropzone ludique + mapping guidé
- Template mapping : table `inscriptions_import_templates` (empreinte en-têtes + eve/com)
## Phases
## Exemple fichier client (référence)
0. **Cadrage** — contrat fichier + unique ID ; stratégie questions C ; carto « signature de dossier » ; inventaire `no_panier` / finances
1. **Fondations** — type événement ; colonnes clé externe + origine ; coquille `pec` sans commande payante
2. **Import** — UI + upsert clé unique + questions (match / création confirmée) → `resultats_*`
3. **Saisie / édition** — fiche rapide ; ouvert aussi sur courses en ligne (jour J)
4. **CT + hub** — sync avec clé fichier ; mini-hub ; accès par tâche ; signature de dossier
5. **Ops** — tests mélange payant vs hors-transaction ; filet même fichier → CT
Fichier type : export ~Échappée (en-têtes déjà normalisés ; loriginal client peut différer).
## Règles agent (pendant le chantier)
| Colonne exemple | Cible MS1 |
|---|---|
| Attendee # | `par_external_id` (clé unique / CT) |
| First Name / Last Name | `par_prenom` / `par_nom` |
| Ticket Type | `epr` (valeurs → épreuves MS1) |
| No dossard | `no_bib` |
| Gender (Male/Female/Other) | `par_sexe` (normaliser à lécriture) |
| Age / Birth Date | `par_age` / `par_naissance` |
| Nationalité, urgence, allergies, condition médicale, langue… | questions (option C) |
- Changement minimal ; référencer **MSIN-4482** dans SQL / commentaires PHP.
- Ne pas brancher ces flux sur le panier / paiement.
- Ne pas réintroduire le waiver « pour faire la signature de dossier ».
- Préférer étendre gestion / hub existants plutôt que doubler les fiches participant.
- SQL : uniquement **dev préprod**, puis Navicat + sync outil (workflow projet).
~1800 lignes, ~1012 valeurs distinctes de Ticket Type (distances / jours).
## Fichiers
| Rôle | Fichier |
|---|---|
| SQL | `sql/MSIN-4482-import-resultats-templates.sql` |
| Logique + UI | `php/inc_fx_import_resultats.php` |
| AJAX | `superadm/ajax_import_resultats.php` |
| Branche fiche eve | `superadm/php/inc_form.php` |
| CSS | `superadm/css/style_blue.css` |
## Hors scope (maintenant)
- Hub promoteur / mini-hub
- Waiver
- Écriture `resultats_*` (prochaine étape après UI)
- Import inverse CT → MS1
## Règles agent
- Changement minimal ; référencer **MSIN-4482**.
- **Pas de panier** pour ces participants — écrire dans `resultats_*`.
- **Ne pas modifier le hub promoteur** tant que ce nest pas redemandé.
- Ne pas inventer de waiver pour « signature de dossier ».
- SQL : **dev préprod** seulement, puis Navicat + sync outil.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
-- MSIN-4482 — Templates mapping import participants (hors transaction)
-- Contexte : UI import fiche événement superadm ; upsert sur clé fichier
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
SET @db := DATABASE();
-- Templates de mapping colonnes (réutilisables par empreinte d'en-têtes)
CREATE TABLE IF NOT EXISTS `inscriptions_import_templates` (
`tpl_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`eve_id` int(10) unsigned NOT NULL DEFAULT 0,
`com_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'MSIN-4482 — 0 = lié événement ; >0 = réutilisable côté client',
`tpl_name` varchar(120) NOT NULL DEFAULT '',
`tpl_header_sig` char(40) NOT NULL DEFAULT '' COMMENT 'sha1 en-têtes normalisés',
`tpl_headers_json` mediumtext NOT NULL,
`tpl_mapping_json` mediumtext NOT NULL COMMENT 'colonnes + valeurs épreuve + questions',
`tpl_actif` tinyint(1) unsigned NOT NULL DEFAULT 1,
`tpl_usa_id` int(10) unsigned NOT NULL DEFAULT 0,
`tpl_creation` datetime NOT NULL,
`tpl_maj` datetime NOT NULL,
PRIMARY KEY (`tpl_id`),
KEY `idx_import_tpl_eve_sig` (`eve_id`, `tpl_header_sig`),
KEY `idx_import_tpl_com_sig` (`com_id`, `tpl_header_sig`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Clé unique fichier (= external_id ChronoTrack) + origine d'entrée
SET @col_ext := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'resultats_participants' AND COLUMN_NAME = 'par_external_id'
);
SET @sql_ext := IF(
@col_ext = 0,
'ALTER TABLE resultats_participants ADD COLUMN par_external_id VARCHAR(64) NULL DEFAULT NULL COMMENT ''MSIN-4482 — clé unique import / CT external_id'' AFTER par_id_original',
'SELECT ''par_external_id already exists'' AS note'
);
PREPARE stmt_ext FROM @sql_ext;
EXECUTE stmt_ext;
DEALLOCATE PREPARE stmt_ext;
SET @col_ori := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'resultats_participants' AND COLUMN_NAME = 'par_origine'
);
SET @sql_ori := IF(
@col_ori = 0,
'ALTER TABLE resultats_participants ADD COLUMN par_origine VARCHAR(32) NULL DEFAULT NULL COMMENT ''MSIN-4482 — import_fichier|saisie_rapide|inscription_en_ligne'' AFTER par_external_id',
'SELECT ''par_origine already exists'' AS note'
);
PREPARE stmt_ori FROM @sql_ori;
EXECUTE stmt_ori;
DEALLOCATE PREPARE stmt_ori;
SET @idx_ext := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'resultats_participants' AND INDEX_NAME = 'idx_par_external_eve'
);
SET @sql_idx := IF(
@idx_ext = 0,
'ALTER TABLE resultats_participants ADD KEY idx_par_external_eve (eve_id, par_external_id)',
'SELECT ''idx_par_external_eve already exists'' AS note'
);
PREPARE stmt_idx FROM @sql_idx;
EXECUTE stmt_idx;
DEALLOCATE PREPARE stmt_idx;

View File

@ -0,0 +1,70 @@
<?php
/**
* MSIN-4482 — AJAX import participants (hors transaction) — superadm.
*/
require_once dirname(__FILE__) . '/php/inc_functions.php';
require_once dirname(__FILE__) . '/../php/inc_fx_import_resultats.php';
$intUsaId = fxSuperadmAjaxAuthOrJsonExit();
if (session_status() === PHP_SESSION_ACTIVE) {
session_write_close();
}
$strAction = trim((string)($_REQUEST['a'] ?? ''));
$intEveId = intval($_REQUEST['eve_id'] ?? 0);
$tabRetour = array('state' => 'error', 'message' => 'action_invalide');
switch ($strAction) {
case 'upload':
if ($intEveId <= 0) {
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
break;
}
if (empty($_FILES['file'])) {
$tabRetour = array('state' => 'error', 'message' => 'Fichier manquant');
break;
}
$tabRetour = fxImportResultatsSaveUpload($intEveId, $_FILES['file']);
break;
case 'save_template':
if ($intEveId <= 0) {
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
break;
}
$strToken = trim((string)($_REQUEST['token'] ?? ''));
$strName = trim((string)($_REQUEST['tpl_name'] ?? ''));
$arrMapping = $_REQUEST['mapping'] ?? array();
if (is_string($arrMapping)) {
$arrDecoded = json_decode($arrMapping, true);
$arrMapping = is_array($arrDecoded) ? $arrDecoded : array();
}
if (!is_array($arrMapping)) {
$arrMapping = array();
}
$tabRetour = fxImportResultatsSaveTemplate($intEveId, $strToken, $strName, $arrMapping, $intUsaId, true);
break;
case 'validate':
if ($intEveId <= 0) {
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
break;
}
$strToken = trim((string)($_REQUEST['token'] ?? ''));
$arrMapping = $_REQUEST['mapping'] ?? array();
if (is_string($arrMapping)) {
$arrDecoded = json_decode($arrMapping, true);
$arrMapping = is_array($arrDecoded) ? $arrDecoded : array();
}
if (!is_array($arrMapping)) {
$arrMapping = array();
}
$tabRetour = fxImportResultatsValidateMapping($intEveId, $strToken, $arrMapping);
break;
default:
$tabRetour = array('state' => 'error', 'message' => 'action_invalide');
break;
}
echo json_encode($tabRetour, JSON_UNESCAPED_UNICODE);

View File

@ -396,4 +396,76 @@ table.rapport tr {background-color: #190d7394;}*/
.superadm-v2-panel__body .custom-control-label {
color: #1e293b;
font-weight: 500;
}
/* MSIN-4482 — Import participants (hors transaction) */
.ms1-import__drop {
align-items: center;
background: linear-gradient(160deg, #f0f9fc 0%, #e4f3f8 100%);
border: 2px dashed #045a6e;
border-radius: 12px;
color: #045a6e;
cursor: pointer;
display: flex;
flex-direction: column;
gap: 0.35rem;
justify-content: center;
min-height: 9.5rem;
padding: 1.25rem 1rem;
text-align: center;
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.ms1-import__drop:hover,
.ms1-import__drop:focus {
background: #e8f6fb;
box-shadow: 0 4px 14px rgba(4, 90, 110, 0.12);
outline: none;
}
.ms1-import__drop.is-drag {
background: #dff3f9;
border-color: #0284a8;
border-style: solid;
box-shadow: 0 0 0 3px rgba(2, 132, 168, 0.2);
transform: scale(1.01);
}
.ms1-import__drop.is-busy {
cursor: wait;
opacity: 0.72;
pointer-events: none;
}
.ms1-import__drop.is-ready {
border-style: solid;
min-height: 4.5rem;
padding: 0.65rem;
}
.ms1-import__drop.is-ready .ms1-import__drop-sub {
display: none;
}
.ms1-import__drop-icon {
font-size: 2rem;
line-height: 1;
opacity: 0.9;
}
.ms1-import__drop-title {
font-size: 1.05rem;
font-weight: 700;
}
.ms1-import__drop-sub {
color: #64748b;
font-size: 0.875rem;
}
.ms1-import__h {
color: #045a6e;
font-weight: 700;
margin-top: 0.35rem;
}
.ms1-import__status.alert {
margin-bottom: 0.75rem;
}
.ms1-import__epr,
.ms1-import__que {
background: #f8fafc;
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 0.75rem 0.85rem;
}

View File

@ -128,6 +128,10 @@ if ($strAction == 'mod') {
));
fxChronotrackApiAdminRenderEventPage($intEveIdForm, $strLangue, true, true);
fxSuperadmV2PanelClose(array('collapse_id' => 'chronotrackApiCollapse'));
// MSIN-4482 — Import participants hors transaction (UI + templates)
require_once(dirname(__FILE__) . '/../../php/inc_fx_import_resultats.php');
fxImportResultatsRenderEventPanel($intEveIdForm);
}
}
@ -153,6 +157,10 @@ if ($strAction == 'mod') {
onclick="if (window.jQuery) { jQuery('#chronotrackApiCollapse').collapse('show'); }">
ChronoTrack
</a>
<a class="btn btn-secondary" href="#import_resultats"
onclick="if (window.jQuery) { jQuery('#importResultatsCollapse').collapse('show'); }">
Import
</a>
<a class="btn btn-secondary" href="#list_eve_extra">
Champs supplémentaires
</a>