Compare commits
25 Commits
fd250996b2
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| aee3467918 | |||
| 087ef47e15 | |||
| aabb419e7d | |||
| 3a7c1f4bb7 | |||
| 36eb81d8e8 | |||
| d4ab2e2dc7 | |||
| 8cd023e224 | |||
| 8788f22a81 | |||
| fd7930b139 | |||
| a68f54be80 | |||
| 038bd7bd26 | |||
| 69a97c0044 | |||
| 26a470fe1e | |||
| 80c791c7e1 | |||
| 05ce600e09 | |||
| c6477f7757 | |||
| 8498af24f9 | |||
| 171a48a411 | |||
| d63c92e36a | |||
| 8929236e85 | |||
| 84e65340b6 | |||
| a1b6fa8911 | |||
| 1ca75a4ed4 | |||
| db7b2e9f46 | |||
| b67fdaee78 |
102
.cursor/rules/projet/10-hors-transaction-import.mdc
Normal file
102
.cursor/rules/projet/10-hors-transaction-import.mdc
Normal file
@ -0,0 +1,102 @@
|
||||
---
|
||||
description: Projet MS1 hors transaction — import / saisie rapide / ChronoTrack (cadrage produit)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Projet MSIN-4482 — entrée hors transaction (import + terrain)
|
||||
|
||||
JIRA : **MSIN-4482**.
|
||||
Orthographe produit : **ChronoTrack**.
|
||||
Doc de cadrage — source de vérité pour le développement de ce chantier.
|
||||
|
||||
## Intention
|
||||
|
||||
Utiliser la suite MS1 (gestion, ChronoTrack, outils promoteur existants) pour des clients **sans** tunnel d’inscription / paiement en ligne MS1.
|
||||
Les participants arrivent surtout par **import fichier** (puis saisie rapide plus tard) ; ensuite sync ChronoTrack.
|
||||
|
||||
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_*`** — **pas de panier / `no_panier`** pour ces lignes |
|
||||
| Type événement | **`inscriptions_types_evenements`** — libellé FR **Sans inscription en ligne** / EN **No online registration** (`sql/MSIN-4482-type-sans-inscription-en-ligne.sql`) ; l’événement pointe via `inscriptions_evenements.te_id` |
|
||||
| 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 l’instant** — hub actuel tel quel |
|
||||
| Qui est promoteur | Opérateur MS1 se met **lui-même** promoteur ; droits clients = kits **minimisés via l’existant** |
|
||||
| Mini-hub | **Plus tard**, au fur et à mesure — pas un livrable immédiat |
|
||||
| Waiver | **Hors scope** |
|
||||
| Édition / saisie rapide | Après l’import (phase suivante) |
|
||||
| Finances | Marqueur d’**origine** ; import / saisie rapide exclus des rapports paiement |
|
||||
|
||||
## Origines participant (marqueur)
|
||||
|
||||
| Code | Canal |
|
||||
|---|---|
|
||||
| `inscription_en_ligne` | Flux payant actuel |
|
||||
| `import_fichier` | Bulk modèle fichier |
|
||||
| `saisie_rapide` | Création unitaire / jour J (plus tard) |
|
||||
|
||||
## Phases (réalignées)
|
||||
|
||||
1. **UI import superadm** — dropzone, mapping colonnes + épreuves, templates, bouton **Importer**
|
||||
2. **Écriture `resultats_*`** — upsert clé unique + questions C (sans panier) — en place
|
||||
3. **Mapping pays** (valeurs fichier → `pay_id`) — **étape 2**
|
||||
4. **ChronoTrack** — push avec `par_external_id`
|
||||
5. **Saisie / édition** — fiche rapide (+ jour J)
|
||||
6. **Hub minimisé** — seulement si/quand nécessaire
|
||||
|
||||
## UI import (décisions)
|
||||
|
||||
- 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)
|
||||
|
||||
## Exemple fichier client (référence)
|
||||
|
||||
Fichier type : export ~Échappée (en-têtes déjà normalisés ; l’original client peut différer).
|
||||
|
||||
| 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) |
|
||||
|
||||
**Visibilité liste / CT** : les coquilles `resultats_epreuves_commandees` doivent avoir **`pec_actif = 1`** (sinon absentes de la liste inscriptions et du sync). Les `I{eve}X…` = faux `no_commande` technique, pas un vrai panier.
|
||||
|
||||
~1800 lignes, ~10–12 valeurs distinctes de Ticket Type (distances / jours).
|
||||
|
||||
## Fichiers
|
||||
|
||||
| Rôle | Fichier |
|
||||
|---|---|
|
||||
| SQL templates | `sql/MSIN-4482-import-resultats-templates.sql` |
|
||||
| SQL type eve | `sql/MSIN-4482-type-sans-inscription-en-ligne.sql` |
|
||||
| SQL fix liste | `sql/MSIN-4482-fix-pec-actif-import.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 n’est pas redemandé.
|
||||
- Ne pas inventer de waiver pour « signature de dossier ».
|
||||
- SQL : **dev préprod** seulement, puis Navicat + sync outil.
|
||||
@ -1,24 +0,0 @@
|
||||
---
|
||||
description: Inbox Drive — demandes préparées sur le téléphone (.cursor/inbox)
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Inbox téléphone → Cursor
|
||||
|
||||
## Emplacement
|
||||
|
||||
- Chemin projet : `.cursor/inbox/` (junction Windows vers Google Drive).
|
||||
- Dossier Drive : `I:\Mon disque\inbox-cursor-ms1inscription` (compte syncé sur `I:`).
|
||||
- Contenu **gitignoré** — notes de travail, pas du code.
|
||||
|
||||
## Quand l’utilisateur dit « traite l’inbox », « nouvelles demandes », « ce que j’ai préparé », etc.
|
||||
|
||||
1. Lister les fichiers `.md` dans `.cursor/inbox/` **sauf** `_TEMPLATE.md`, `README.md`, et tout ce qui est sous `done/`.
|
||||
2. Présenter un résumé court de chaque demande (titre + objectif).
|
||||
3. Demander laquelle traiter (ou traiter la plus récente si une seule / si l’utilisateur le dit).
|
||||
4. Lire le fichier choisi comme brief de tâche ; demander le **JIRA** s’il manque avant de modifier le code.
|
||||
5. Après traitement réussi : proposer de déplacer le fichier vers `.cursor/inbox/done/`.
|
||||
|
||||
## Hors de ces phrases
|
||||
|
||||
Ne pas scanner l’inbox à chaque message. Les fichiers sont visibles dans l’explorateur Cursor.
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -13,9 +13,6 @@ dist/
|
||||
# IDE — ne jamais committer les connexions SQL (copier settings.json.example)
|
||||
.vscode/settings.json
|
||||
|
||||
# Inbox téléphone (junction → Google Drive) — briefs locaux, pas du code
|
||||
.cursor/inbox/
|
||||
|
||||
# Compiled Java class files
|
||||
*.class
|
||||
|
||||
|
||||
1
assets/dossard_templates/.gitkeep
Normal file
1
assets/dossard_templates/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
|
||||
BIN
assets/dossard_templates/default.jpg
Normal file
BIN
assets/dossard_templates/default.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
BIN
assets/dossard_templates/mamie-joe-papi-fan.jpg
Normal file
BIN
assets/dossard_templates/mamie-joe-papi-fan.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
49
dossard_print.php
Normal file
49
dossard_print.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4485 — Endpoint PDF dossard (1 participant).
|
||||
* Isolé du tunnel / panier ; droit inscriptions_gestion.dossard_print.
|
||||
*/
|
||||
|
||||
session_start();
|
||||
|
||||
$_SERVER['PHP_SELF'] = '/compte.php';
|
||||
$_SERVER['SCRIPT_NAME'] = '/compte.php';
|
||||
|
||||
require_once __DIR__ . '/php/inc_fonctions.php';
|
||||
require_once __DIR__ . '/php/inc_fx_dossard_print.php';
|
||||
|
||||
$strLangue = $_SESSION['lang'] ?? 'fr';
|
||||
$reqLang = $_REQUEST['lang'] ?? $_REQUEST['lng'] ?? '';
|
||||
if (!empty($reqLang) && in_array($reqLang, array('fr', 'en'), true)) {
|
||||
$strLangue = $reqLang;
|
||||
}
|
||||
$vPage = 'compte.php';
|
||||
$vtexte_page = obtenirTextepage($vPage, $strLangue, 2);
|
||||
|
||||
$intParId = intval($_GET['par_id'] ?? $_POST['par_id'] ?? 0);
|
||||
$intEveId = intval($_GET['eve_id'] ?? $_POST['eve_id'] ?? 0);
|
||||
$intComId = intval($_SESSION['com_id'] ?? 0);
|
||||
|
||||
if ($intComId <= 0 || $intParId <= 0 || $intEveId <= 0) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Accès refusé.';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!fxDossardPrintCan($intComId, $intEveId)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Droit insuffisant.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$arrRow = fxDossardPrintLoadParticipant($intParId, $intEveId);
|
||||
if ($arrRow === null) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Participant introuvable.';
|
||||
exit;
|
||||
}
|
||||
|
||||
fxDossardPrintOutputPdf($arrRow);
|
||||
BIN
images/te_sans_inscription_en_ligne.png
Normal file
BIN
images/te_sans_inscription_en_ligne.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 821 KiB |
302
php/inc_fx_dossard_print.php
Normal file
302
php/inc_fx_dossard_print.php
Normal file
@ -0,0 +1,302 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4485 — Impression dossard PDF (1 participant).
|
||||
*
|
||||
* Format FINAL : demi-feuille Lettre coupée sur le 11"
|
||||
* → papier 8.5" large × 5.5" haut (paysage)
|
||||
* → PDF page = exactement cette taille
|
||||
* → fond pleine page + n° / nom / QR alignés
|
||||
*
|
||||
* Impression : papier personnalisé 8.5 × 5.5, échelle 100 %, sans « ajuster à la page ».
|
||||
*/
|
||||
|
||||
define('MSIN_DOSSARD_PRINT_PERM', 'inscriptions_gestion.dossard_print');
|
||||
|
||||
/**
|
||||
* Taille page = feuille coupée (pouces → mm).
|
||||
* Lettre 8.5×11 coupée en 2 sur le 11" → 8.5 × 5.5.
|
||||
*
|
||||
* @return array{0:float,1:float} [largeur, hauteur]
|
||||
*/
|
||||
function fxDossardPrintPageSizeMm()
|
||||
{
|
||||
return array(8.5 * 25.4, 5.5 * 25.4);
|
||||
}
|
||||
|
||||
/** Dossier des fonds JPG/PNG (même ratio 8.5:5.5 recommandé). */
|
||||
function fxDossardPrintTemplatesDir()
|
||||
{
|
||||
return dirname(__DIR__) . '/assets/dossard_templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fond : eve_{id}.jpg|png sinon default.jpg|png.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
function fxDossardPrintResolveTemplatePath($intEveId)
|
||||
{
|
||||
$strDir = fxDossardPrintTemplatesDir();
|
||||
$intEveId = intval($intEveId);
|
||||
$arrCandidates = array();
|
||||
if ($intEveId > 0) {
|
||||
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.jpg';
|
||||
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.jpeg';
|
||||
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.png';
|
||||
}
|
||||
$arrCandidates[] = $strDir . '/default.jpg';
|
||||
$arrCandidates[] = $strDir . '/default.jpeg';
|
||||
$arrCandidates[] = $strDir . '/default.png';
|
||||
|
||||
foreach ($arrCandidates as $strPath) {
|
||||
if (is_file($strPath)) {
|
||||
return $strPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type FPDF d'après le fichier réel.
|
||||
*
|
||||
* @return string JPG|PNG|GIF|''
|
||||
*/
|
||||
function fxDossardPrintImageType($strPath)
|
||||
{
|
||||
$arrInfo = @getimagesize($strPath);
|
||||
if (!is_array($arrInfo) || empty($arrInfo[2])) {
|
||||
return '';
|
||||
}
|
||||
if ((int)$arrInfo[2] === IMAGETYPE_JPEG) {
|
||||
return 'JPG';
|
||||
}
|
||||
if ((int)$arrInfo[2] === IMAGETYPE_PNG) {
|
||||
return 'PNG';
|
||||
}
|
||||
if ((int)$arrInfo[2] === IMAGETYPE_GIF) {
|
||||
return 'GIF';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/** Texte FPDF (core fonts = Windows-1252). */
|
||||
function fxDossardPrintPdfText($str)
|
||||
{
|
||||
$str = (string)$str;
|
||||
if (function_exists('iconv')) {
|
||||
$strConverted = @iconv('UTF-8', 'Windows-1252//TRANSLIT', $str);
|
||||
if ($strConverted !== false) {
|
||||
return $strConverted;
|
||||
}
|
||||
}
|
||||
if (function_exists('mb_convert_encoding')) {
|
||||
return (string)mb_convert_encoding($str, 'Windows-1252', 'UTF-8');
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/** Droit impression (hors kit total). */
|
||||
function fxDossardPrintCan($intComId, $intEveId)
|
||||
{
|
||||
if (!function_exists('fxEveAccesHasPermission')) {
|
||||
require_once __DIR__ . '/inc_fx_eve_acces.php';
|
||||
}
|
||||
|
||||
return fxEveAccesHasPermission(intval($intComId), intval($intEveId), MSIN_DOSSARD_PRINT_PERM);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
function fxDossardPrintLoadParticipant($intParId, $intEveId)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
$intParId = intval($intParId);
|
||||
$intEveId = intval($intEveId);
|
||||
if ($intParId <= 0 || $intEveId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sql = 'SELECT p.par_id, p.par_prenom, p.par_nom, p.no_bib, p.eve_id, p.epr_id, p.pec_id,
|
||||
e.pec_id_original, e.pec_actif, e.is_cancelled
|
||||
FROM resultats_participants p
|
||||
INNER JOIN resultats_epreuves_commandees e
|
||||
ON e.pec_id_original = p.pec_id AND e.epr_id = p.epr_id
|
||||
WHERE p.par_id = ' . $intParId . '
|
||||
AND p.eve_id = ' . $intEveId . '
|
||||
LIMIT 1';
|
||||
|
||||
$row = $objDatabase->fxGetRow($sql);
|
||||
|
||||
return ($row === null || $row === false) ? null : $row;
|
||||
}
|
||||
|
||||
function fxDossardPrintQrUrl($intEveId)
|
||||
{
|
||||
if (!function_exists('fxKcEveResultatsQrUrl')) {
|
||||
require_once __DIR__ . '/inc_fx_eve_extra.php';
|
||||
}
|
||||
|
||||
return fxKcEveResultatsQrUrl(intval($intEveId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false chemin PNG temp
|
||||
*/
|
||||
function fxDossardPrintBuildQrTempFile($intEveId)
|
||||
{
|
||||
require_once __DIR__ . '/../libs/phpqrcode-master/qrlib.php';
|
||||
|
||||
$strUrl = fxDossardPrintQrUrl($intEveId);
|
||||
if ($strUrl === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$strTmp = tempnam(sys_get_temp_dir(), 'ms1dossardqr_');
|
||||
if ($strTmp === false) {
|
||||
return false;
|
||||
}
|
||||
$strPng = $strTmp . '.png';
|
||||
@unlink($strTmp);
|
||||
|
||||
QRcode::png($strUrl, $strPng, QR_ECLEVEL_M, 6, 1);
|
||||
if (!is_file($strPng)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $strPng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout page 8.5" × 5.5" (mm) + overlays.
|
||||
* Coordonnées calibrées pour le template comic (case QR bas-droite).
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function fxDossardPrintLayout()
|
||||
{
|
||||
$arrSize = fxDossardPrintPageSizeMm();
|
||||
$fltW = $arrSize[0]; // 215.9
|
||||
$fltH = $arrSize[1]; // 139.7
|
||||
|
||||
// Case QR du design : bas-droite (~1.1" dans le cadre)
|
||||
$fltQr = 28.0;
|
||||
|
||||
return array(
|
||||
'page_w' => $fltW,
|
||||
'page_h' => $fltH,
|
||||
// N° centré dans la zone blanche centrale
|
||||
'bib_y' => 52,
|
||||
'bib_font' => 72,
|
||||
'bib_h' => 28,
|
||||
// Nom sous le numéro
|
||||
'name_y' => 84,
|
||||
'name_font' => 14,
|
||||
// QR dans la case « SCANNEZ POUR RÉSULTATS »
|
||||
'qr_size' => $fltQr,
|
||||
'qr_x' => $fltW - $fltQr - 11.5,
|
||||
'qr_y' => $fltH - $fltQr - 14.5,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère et envoie le PDF (exit).
|
||||
*
|
||||
* @param array $arrRow
|
||||
*/
|
||||
function fxDossardPrintOutputPdf($arrRow)
|
||||
{
|
||||
$intEveId = intval($arrRow['eve_id'] ?? 0);
|
||||
$strTemplate = fxDossardPrintResolveTemplatePath($intEveId);
|
||||
if ($strTemplate === null) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Template dossard introuvable (assets/dossard_templates/default.jpg).';
|
||||
exit;
|
||||
}
|
||||
|
||||
$strFpdf = dirname(__DIR__) . '/fpdf182/fpdf.php';
|
||||
if (!is_file($strFpdf)) {
|
||||
$strDocRoot = $_SERVER['DOCUMENT_ROOT'] ?? '';
|
||||
$strFpdf = rtrim((string)$strDocRoot, '/\\') . '/fpdf182/fpdf.php';
|
||||
}
|
||||
if (!is_file($strFpdf)) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'FPDF introuvable (fpdf182/fpdf.php).';
|
||||
exit;
|
||||
}
|
||||
require_once $strFpdf;
|
||||
|
||||
$arrLayout = fxDossardPrintLayout();
|
||||
$strBib = trim((string)($arrRow['no_bib'] ?? ''));
|
||||
if ($strBib === '' || $strBib === '0') {
|
||||
$strBib = '-';
|
||||
}
|
||||
$strPrenom = trim(function_exists('fxUnescape') ? fxUnescape($arrRow['par_prenom'] ?? '') : (string)($arrRow['par_prenom'] ?? ''));
|
||||
$strNom = trim(function_exists('fxUnescape') ? fxUnescape($arrRow['par_nom'] ?? '') : (string)($arrRow['par_nom'] ?? ''));
|
||||
$strName = trim($strPrenom . ' ' . $strNom);
|
||||
|
||||
$strQrFile = fxDossardPrintBuildQrTempFile($intEveId);
|
||||
$strTplType = fxDossardPrintImageType($strTemplate);
|
||||
|
||||
// Page = feuille coupée 8.5" × 5.5".
|
||||
// FPDF (_getpagesize) range si w>h ; 'L' remet w=8.5 h=5.5.
|
||||
$pdf = new FPDF('L', 'mm', array($arrLayout['page_w'], $arrLayout['page_h']));
|
||||
$pdf->SetAutoPageBreak(false);
|
||||
$pdf->SetMargins(0, 0, 0);
|
||||
$pdf->AddPage();
|
||||
|
||||
// Fond pleine page — ratio template ≈ 8.5:5.5 → pas de déformation
|
||||
$pdf->Image(
|
||||
$strTemplate,
|
||||
0,
|
||||
0,
|
||||
$arrLayout['page_w'],
|
||||
$arrLayout['page_h'],
|
||||
$strTplType
|
||||
);
|
||||
|
||||
// Numéro
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
$pdf->SetFont('Helvetica', 'B', intval($arrLayout['bib_font']));
|
||||
$pdf->SetXY(20, $arrLayout['bib_y']);
|
||||
$pdf->Cell(
|
||||
$arrLayout['page_w'] - 40,
|
||||
$arrLayout['bib_h'],
|
||||
fxDossardPrintPdfText($strBib),
|
||||
0,
|
||||
0,
|
||||
'C'
|
||||
);
|
||||
|
||||
// Nom
|
||||
if ($strName !== '') {
|
||||
$pdf->SetFont('Helvetica', 'B', intval($arrLayout['name_font']));
|
||||
$pdf->SetXY(20, $arrLayout['name_y']);
|
||||
$pdf->Cell($arrLayout['page_w'] - 40, 8, fxDossardPrintPdfText($strName), 0, 0, 'C');
|
||||
}
|
||||
|
||||
// QR dans la case du template
|
||||
if ($strQrFile !== false) {
|
||||
$pdf->Image(
|
||||
$strQrFile,
|
||||
$arrLayout['qr_x'],
|
||||
$arrLayout['qr_y'],
|
||||
$arrLayout['qr_size'],
|
||||
$arrLayout['qr_size'],
|
||||
'PNG'
|
||||
);
|
||||
@unlink($strQrFile);
|
||||
}
|
||||
|
||||
$strSafeBib = preg_replace('/[^a-zA-Z0-9_-]+/', '-', $strBib);
|
||||
$strFilename = 'dossard-' . $strSafeBib . '-par' . intval($arrRow['par_id']) . '.pdf';
|
||||
|
||||
$pdf->Output('I', $strFilename);
|
||||
exit;
|
||||
}
|
||||
@ -410,6 +410,52 @@ function fxEveAccesCatalogPermIsActive($strPermKey)
|
||||
return ($intNb != null && intval($intNb) > 0);
|
||||
}
|
||||
|
||||
/** MSIN-4484 — Colonne catalogue perm_grants_all presente ? */
|
||||
function fxEveAccesHasPermGrantsAllColumn()
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
static $blnHas = null;
|
||||
if ($blnHas !== null) {
|
||||
return $blnHas;
|
||||
}
|
||||
|
||||
if (!fxEveAccesIsEnabled()) {
|
||||
$blnHas = false;
|
||||
return $blnHas;
|
||||
}
|
||||
|
||||
$tab = $objDatabase->fxGetResults("SHOW COLUMNS FROM inscriptions_eve_permissions LIKE 'perm_grants_all'");
|
||||
$blnHas = ($tab != null && count($tab) > 0);
|
||||
|
||||
return $blnHas;
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4484 — Cle active et incluse dans un kit role_grants_all ?
|
||||
* perm_grants_all = 0 → hors propagation (matrice / extra seulement).
|
||||
*/
|
||||
function fxEveAccesCatalogPermPropagatesWithGrantsAll($strPermKey)
|
||||
{
|
||||
global $objDatabase;
|
||||
|
||||
if (!fxEveAccesIsEnabled() || trim((string)$strPermKey) === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fxEveAccesHasPermGrantsAllColumn()) {
|
||||
return fxEveAccesCatalogPermIsActive($strPermKey);
|
||||
}
|
||||
|
||||
$sql = "SELECT COUNT(*) FROM inscriptions_eve_permissions
|
||||
WHERE perm_key = '" . $objDatabase->fxEscape($strPermKey) . "'
|
||||
AND perm_actif = 1
|
||||
AND perm_grants_all = 1";
|
||||
$intNb = $objDatabase->fxGetVar($sql);
|
||||
|
||||
return ($intNb != null && intval($intNb) > 0);
|
||||
}
|
||||
|
||||
/** Compte avec au moins un kit role_grants_all actif sur cet evenement. */
|
||||
function fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId)
|
||||
{
|
||||
@ -484,8 +530,10 @@ function fxEveAccesHasPermission($intComId, $intEveId, $strPermKey)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId)) {
|
||||
return fxEveAccesCatalogPermIsActive($strPermKey);
|
||||
// MSIN-4484 — kit total = cles propagées ; cles hors kit total → matrice / extra seulement.
|
||||
if (fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId)
|
||||
&& fxEveAccesCatalogPermPropagatesWithGrantsAll($strPermKey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$sql = "SELECT COUNT(*) FROM " . fxEveAccesPermSql() . "
|
||||
@ -505,14 +553,13 @@ function fxEveAccesHasAnyPermission($intComId, $intEveId, $arrPermKeys)
|
||||
return false;
|
||||
}
|
||||
|
||||
// MSIN-4484 — kit total pour les cles propagées ; sinon (ou cles hors total) → explicite.
|
||||
if (fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId)) {
|
||||
foreach ($arrPermKeys as $strKey) {
|
||||
if (fxEveAccesCatalogPermIsActive($strKey)) {
|
||||
if (fxEveAccesCatalogPermPropagatesWithGrantsAll($strKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$arrEsc = array();
|
||||
@ -583,8 +630,14 @@ function fxEveAccesGetCatalogPermissions($strScope = null, $blnActifOnly = true)
|
||||
return array();
|
||||
}
|
||||
|
||||
$sql = "SELECT perm_key, perm_group, perm_scope, perm_label_fr, perm_label_en, perm_phase, perm_tri
|
||||
FROM inscriptions_eve_permissions";
|
||||
// MSIN-4484 — perm_grants_all pour UI matrice (badge hors kit total).
|
||||
$strGrantsCol = fxEveAccesHasPermGrantsAllColumn()
|
||||
? ', perm_grants_all'
|
||||
: ', 1 AS perm_grants_all';
|
||||
|
||||
$sql = "SELECT perm_key, perm_group, perm_scope, perm_label_fr, perm_label_en, perm_phase, perm_tri"
|
||||
. $strGrantsCol
|
||||
. " FROM inscriptions_eve_permissions";
|
||||
|
||||
$arrWhere = array();
|
||||
if ($blnActifOnly) {
|
||||
@ -1703,7 +1756,18 @@ function fxEveAccesShowCompteForm($intComId)
|
||||
$arrAcces = fxEveAccesListByComId($intComId);
|
||||
$arrExtras = fxEveAccesListExtraByComId($intComId);
|
||||
$arrRoles = fxEveAccesGetRoles(true);
|
||||
// MSIN-4485 — extras = actions ; clés hors kit total en tête de liste.
|
||||
$arrActionPerms = fxEveAccesGetCatalogPermissions('action');
|
||||
if ($arrActionPerms != null && is_array($arrActionPerms)) {
|
||||
usort($arrActionPerms, function ($a, $b) {
|
||||
$intA = isset($a['perm_grants_all']) ? intval($a['perm_grants_all']) : 1;
|
||||
$intB = isset($b['perm_grants_all']) ? intval($b['perm_grants_all']) : 1;
|
||||
if ($intA !== $intB) {
|
||||
return $intA <=> $intB; // 0 (hors total) avant 1
|
||||
}
|
||||
return strcmp((string)($a['perm_label_fr'] ?? ''), (string)($b['perm_label_fr'] ?? ''));
|
||||
});
|
||||
}
|
||||
$strT = urlencode($_GET['t'] ?? '');
|
||||
$intComId = intval($intComId);
|
||||
|
||||
@ -1755,8 +1819,13 @@ function fxEveAccesShowCompteForm($intComId)
|
||||
$strPermOptions = '<option value="">—</option>';
|
||||
if ($arrActionPerms != null) {
|
||||
foreach ($arrActionPerms as $rowPerm) {
|
||||
$blnHorsTotal = isset($rowPerm['perm_grants_all']) && intval($rowPerm['perm_grants_all']) === 0;
|
||||
$strLabel = (string)($rowPerm['perm_label_fr'] ?? $rowPerm['perm_key']);
|
||||
if ($blnHorsTotal) {
|
||||
$strLabel = '★ ' . $strLabel . ' (hors kit total)';
|
||||
}
|
||||
$strPermOptions .= '<option value="' . htmlspecialchars($rowPerm['perm_key'], ENT_QUOTES, 'UTF-8') . '">'
|
||||
. htmlspecialchars($rowPerm['perm_label_fr'], ENT_QUOTES, 'UTF-8') . '</option>';
|
||||
. htmlspecialchars($strLabel, ENT_QUOTES, 'UTF-8') . '</option>';
|
||||
}
|
||||
}
|
||||
require_once __DIR__ . '/inc_fx_superadm_v2_ui.php';
|
||||
@ -1827,7 +1896,7 @@ function fxEveAccesShowCompteForm($intComId)
|
||||
</td>
|
||||
<td class="align-middle text-right text-nowrap">
|
||||
<?php if ($blnHasKit) { ?>
|
||||
<select id="eve-extra-<?= $intEveId ?>" class="custom-select custom-select-sm d-inline-block" style="width:96px;vertical-align:middle;" aria-label="Droit individuel">
|
||||
<select id="eve-extra-<?= $intEveId ?>" class="custom-select custom-select-sm d-inline-block" style="width:220px;max-width:40vw;vertical-align:middle;" aria-label="Droit individuel">
|
||||
<?= $strPermOptions ?>
|
||||
</select>
|
||||
<button type="button" class="btn btn-outline-warning btn-sm py-0 btn-eve-add-extra" data-eve-id="<?= $intEveId ?>" title="Ajouter un droit individuel" style="border-color:#e67e22;color:#e67e22;">+</button>
|
||||
|
||||
2737
php/inc_fx_import_resultats.php
Normal file
2737
php/inc_fx_import_resultats.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -71,11 +71,7 @@ function fxInscrGestionCanDo($intComId, $intEveId, $strActionPerm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// MSIN-4401 — kit propriétaire / total (grants_all) = toutes les actions, sans cocher chaque droit.
|
||||
if (fxEveAccesComEventHasGrantsAllKit(intval($intComId), intval($intEveId))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// MSIN-4401 / MSIN-4484 — kit total via HasPermission (respecte perm_grants_all = 0).
|
||||
if (fxEveAccesHasPermission(intval($intComId), intval($intEveId), $strActionPerm)) {
|
||||
return true;
|
||||
}
|
||||
@ -177,12 +173,7 @@ function fxInscrGestionCanScan($intComId, $intEveId) {
|
||||
$intComId = intval($intComId);
|
||||
$intEveId = intval($intEveId);
|
||||
|
||||
// MSIN-4401 — kit propriétaire / total (grants_all) = tout, sans cocher chaque droit.
|
||||
if (fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Même règle hub / compte /mobile : droit explicite scanner QR.
|
||||
// MSIN-4401 / MSIN-4484 — kit total via HasAnyPermission (respecte perm_grants_all = 0).
|
||||
if (fxEveAccesHasAnyPermission($intComId, $intEveId, fxInscrGestionPermKeysScan())) {
|
||||
return true;
|
||||
}
|
||||
@ -535,6 +526,11 @@ function fxInscrGestionFallbackTexte($strClef, $strLangue) {
|
||||
'inscr_gestion_audit_field_is_cancelled' => $blnFr ? 'Annulation' : 'Cancellation',
|
||||
// MSIN-4328 — bouton superadmin vers fiche ChronoTrack
|
||||
'inscr_gestion_btn_chronotrack' => $blnFr ? 'Voir dans ChronoTrack' : 'View in ChronoTrack',
|
||||
// MSIN-4485 — impression dossard PDF
|
||||
'inscr_gestion_btn_dossard_print' => $blnFr ? 'Imprimer le dossard' : 'Print bib',
|
||||
'inscr_gestion_btn_dossard_print_title' => $blnFr
|
||||
? 'Générer le PDF du dossard'
|
||||
: 'Generate bib PDF',
|
||||
'inscr_gestion_list_count_one' => $blnFr ? '%d inscription' : '%d registration',
|
||||
'inscr_gestion_list_count_many' => $blnFr ? '%d inscriptions' : '%d registrations',
|
||||
'inscr_gestion_list_page' => $blnFr ? 'Page %d / %d' : 'Page %d / %d',
|
||||
@ -1254,6 +1250,7 @@ function fxInscrGestionFetchFicheProfile($arrRow, $strLangue) {
|
||||
|
||||
$intParId = (int)$arrRow['par_id'];
|
||||
$intPecId = (int)$arrRow['pec_id_original'];
|
||||
$intPecPk = isset($arrRow['pec_id']) ? (int)$arrRow['pec_id'] : 0;
|
||||
$intEveId = (int)$arrRow['eve_id'];
|
||||
$strLangCol = ($strLangue === 'en') ? 'en' : 'fr';
|
||||
|
||||
@ -1290,6 +1287,15 @@ function fxInscrGestionFetchFicheProfile($arrRow, $strLangue) {
|
||||
}
|
||||
|
||||
$intParIdOriginal = (int)$tabProfile['par_id_original'];
|
||||
|
||||
// MSIN-4482 — hors tx : accepter pec_id PK ou pec_id_original (réponses import).
|
||||
// Pas de IFNULL(que_actif) / pas de requête te_nom ici : une SQL en erreur = plantage
|
||||
// (fxGetResults → mysqli_num_rows(false)).
|
||||
$strPecClause = 'rq.pec_id = ' . $intPecId;
|
||||
if ($intPecPk > 0 && $intPecPk !== $intPecId) {
|
||||
$strPecClause = '(rq.pec_id = ' . $intPecId . ' OR rq.pec_id = ' . $intPecPk . ')';
|
||||
}
|
||||
|
||||
$sqlQuestions = 'SELECT rq.que_choix_' . $strLangCol . ' AS que_choix,'
|
||||
. ' iq.que_cart_label_' . $strLangCol . ' AS que_label,'
|
||||
. ' rq.que_question_' . $strLangCol . ' AS que_question,'
|
||||
@ -1298,7 +1304,7 @@ function fxInscrGestionFetchFicheProfile($arrRow, $strLangue) {
|
||||
. ' INNER JOIN inscriptions_questions iq ON iq.que_id = rq.que_id'
|
||||
. ' WHERE iq.que_cart_show = 1'
|
||||
. ' AND iq.eve_id = ' . $intEveId
|
||||
. ' AND rq.pec_id = ' . $intPecId
|
||||
. ' AND ' . $strPecClause
|
||||
. ' AND (rq.par_id = ' . $intParId . ' OR rq.par_id = ' . $intParIdOriginal . ')'
|
||||
. ' AND rq.que_actif = 1'
|
||||
. " AND TRIM(COALESCE(rq.que_choix_" . $strLangCol . ", '')) <> ''"
|
||||
@ -2462,6 +2468,21 @@ function fxInscrGestionRenderFiche($intEveId, $strLangue, $strBaseUrl, $arrReq,
|
||||
}
|
||||
}
|
||||
}
|
||||
// MSIN-4485 — Imprimer le dossard (droit hors kit total)
|
||||
if (!function_exists('fxDossardPrintCan')) {
|
||||
require_once __DIR__ . '/inc_fx_dossard_print.php';
|
||||
}
|
||||
if (fxDossardPrintCan($intComId, $intRowEveId)) {
|
||||
$strPrintUrl = rtrim((string)$vDomaine, '/') . '/dossard_print.php'
|
||||
. '?par_id=' . intval($arrRow['par_id'])
|
||||
. '&eve_id=' . intval($intRowEveId);
|
||||
echo ' <a class="btn btn-outline-primary btn-sm inscr-gestion-btn-dossard-print" href="'
|
||||
. fxInscrGestionEsc($strPrintUrl) . '" target="_blank" rel="noopener noreferrer"'
|
||||
. ' title="' . fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_btn_dossard_print_title')) . '">'
|
||||
. '<i class="fa fa-print" aria-hidden="true"></i> '
|
||||
. fxInscrGestionEsc(fxInscrGestionT('inscr_gestion_btn_dossard_print'))
|
||||
. '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
$strDetailsType = $blnEquipe && fxIsParEquipe($arrRow['pec_id_original'], $arrRow['epr_id'])
|
||||
|
||||
@ -642,10 +642,10 @@ function fxPromoteurHubGetEventLinks($arrItem, $strLangue)
|
||||
$blnLegacyPromoteur = fxIsPromoteur($intComId, $intEveId);
|
||||
// Menus « tout ouvert » legacy seulement si PAS de kit V2 sur cet événement
|
||||
$blnLegacyMenus = $blnLegacyPromoteur && !$blnHasV2;
|
||||
$blnGrantsAll = $blnHasV2 && fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId);
|
||||
|
||||
$fnCan = function ($arrKeys) use ($intComId, $intEveId, $blnSuper, $blnLegacyMenus, $blnGrantsAll) {
|
||||
if ($blnSuper || $blnLegacyMenus || $blnGrantsAll) {
|
||||
// MSIN-4484 — kit total : passer par HasAnyPermission (exclut perm_grants_all = 0).
|
||||
$fnCan = function ($arrKeys) use ($intComId, $intEveId, $blnSuper, $blnLegacyMenus) {
|
||||
if ($blnSuper || $blnLegacyMenus) {
|
||||
return true;
|
||||
}
|
||||
return fxEveAccesHasAnyPermission($intComId, $intEveId, $arrKeys);
|
||||
@ -1587,7 +1587,8 @@ function fxPromoteurHubCanViewDashboard($intComId, $intEveId)
|
||||
require_once __DIR__ . '/inc_fonctions.php';
|
||||
}
|
||||
|
||||
if (fxEveAccesComEventHasGrantsAllKit($intComId, $intEveId)) {
|
||||
// MSIN-4484 — kit total respecté via HasPermission (perm_grants_all).
|
||||
if (fxEveAccesHasPermission($intComId, $intEveId, 'hub.dashboard')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1596,7 +1597,7 @@ function fxPromoteurHubCanViewDashboard($intComId, $intEveId)
|
||||
return true;
|
||||
}
|
||||
|
||||
return fxEveAccesHasPermission($intComId, $intEveId, 'hub.dashboard');
|
||||
return false;
|
||||
}
|
||||
|
||||
function fxPromoteurHubRenderEventCard($arrItem, $strLangue, $blnArchived = false)
|
||||
|
||||
108
sql/MSIN-4482-fix-pec-actif-import.sql
Normal file
108
sql/MSIN-4482-fix-pec-actif-import.sql
Normal file
@ -0,0 +1,108 @@
|
||||
-- MSIN-4482 — Réparer liste inscriptions pour imports hors transaction
|
||||
-- Contexte : données en BD mais liste / « rapport » trop bas
|
||||
-- Liste = p.pec_id = e.pec_id_original AND pec_actif = 1
|
||||
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
|
||||
--
|
||||
-- AVANT : remplacer 1284 par l'eve_id concerné.
|
||||
-- IMPORTANT : ne PAS forcer pec_id_original = pec_id en masse (UNIQUE pec_id_original → 1062).
|
||||
|
||||
SET @eve := 1284;
|
||||
|
||||
-- ========== DIAGNOSTIC ==========
|
||||
|
||||
SELECT COUNT(*) AS n_participants_import
|
||||
FROM resultats_participants
|
||||
WHERE eve_id = @eve AND par_origine = 'import_fichier';
|
||||
|
||||
SELECT COUNT(*) AS n_join_ok
|
||||
FROM resultats_epreuves_commandees e, resultats_participants p
|
||||
WHERE e.eve_id = @eve
|
||||
AND p.pec_id = e.pec_id_original
|
||||
AND p.par_origine = 'import_fichier'
|
||||
AND p.rol_id NOT IN (3, 4)
|
||||
AND e.is_cancelled = 0;
|
||||
|
||||
SELECT COUNT(*) AS n_visibles_liste
|
||||
FROM resultats_epreuves_commandees e, resultats_participants p
|
||||
WHERE e.eve_id = @eve
|
||||
AND p.pec_id = e.pec_id_original
|
||||
AND p.par_origine = 'import_fichier'
|
||||
AND p.rol_id NOT IN (3, 4)
|
||||
AND e.is_cancelled = 0
|
||||
AND e.pec_actif = 1;
|
||||
|
||||
-- Accroche technique import (p.pec_id = e.pec_id) avant réparation
|
||||
SELECT COUNT(*) AS n_lie_via_pec_id
|
||||
FROM resultats_epreuves_commandees e
|
||||
INNER JOIN resultats_participants p
|
||||
ON p.eve_id = e.eve_id AND p.pec_id = e.pec_id
|
||||
WHERE e.eve_id = @eve
|
||||
AND p.par_origine = 'import_fichier';
|
||||
|
||||
-- ========== FIX A — pec_actif = 1 (AVANT de bouger p.pec_id) ==========
|
||||
UPDATE resultats_epreuves_commandees e
|
||||
INNER JOIN resultats_participants p
|
||||
ON p.eve_id = e.eve_id
|
||||
AND p.pec_id = e.pec_id
|
||||
AND p.par_origine = 'import_fichier'
|
||||
SET e.pec_actif = 1
|
||||
WHERE e.eve_id = @eve
|
||||
AND e.is_cancelled = 0
|
||||
AND IFNULL(e.pec_actif, 0) <> 1;
|
||||
|
||||
UPDATE resultats_epreuves_commandees e
|
||||
SET e.pec_actif = 1
|
||||
WHERE e.eve_id = @eve
|
||||
AND e.no_panier LIKE CONCAT('I', @eve, 'X%')
|
||||
AND e.is_cancelled = 0
|
||||
AND IFNULL(e.pec_actif, 0) <> 1;
|
||||
|
||||
-- ========== FIX B — join sans toucher UNIQUE pec_id_original ==========
|
||||
-- L'import a souvent p.pec_id = e.pec_id alors que la liste veut p.pec_id = e.pec_id_original.
|
||||
-- On aligne le participant sur pec_id_original (valeur déjà unique).
|
||||
UPDATE resultats_participants p
|
||||
INNER JOIN resultats_epreuves_commandees e
|
||||
ON e.eve_id = p.eve_id
|
||||
AND e.pec_id = p.pec_id
|
||||
SET p.pec_id = e.pec_id_original
|
||||
WHERE p.eve_id = @eve
|
||||
AND p.par_origine = 'import_fichier'
|
||||
AND e.pec_id_original > 0
|
||||
AND p.pec_id <> e.pec_id_original;
|
||||
|
||||
-- Optionnel / sûr : pec_id_original := pec_id SEULEMENT s'il n'y a PAS de collision UNIQUE
|
||||
UPDATE resultats_epreuves_commandees e
|
||||
INNER JOIN resultats_participants p
|
||||
ON p.eve_id = e.eve_id
|
||||
AND (p.pec_id = e.pec_id OR p.pec_id = e.pec_id_original)
|
||||
AND p.par_origine = 'import_fichier'
|
||||
LEFT JOIN resultats_epreuves_commandees conflict
|
||||
ON conflict.pec_id_original = e.pec_id
|
||||
AND conflict.pec_id <> e.pec_id
|
||||
SET e.pec_id_original = e.pec_id
|
||||
WHERE e.eve_id = @eve
|
||||
AND e.pec_id_original <> e.pec_id
|
||||
AND conflict.pec_id IS NULL;
|
||||
|
||||
-- Si l'optionnel a bougé pec_id_original, ré-aligner p.pec_id
|
||||
UPDATE resultats_participants p
|
||||
INNER JOIN resultats_epreuves_commandees e
|
||||
ON e.eve_id = p.eve_id
|
||||
AND e.no_panier LIKE CONCAT('I', @eve, 'X%')
|
||||
AND e.no_panier = p.no_panier
|
||||
SET p.pec_id = e.pec_id_original
|
||||
WHERE p.eve_id = @eve
|
||||
AND p.par_origine = 'import_fichier'
|
||||
AND e.pec_id_original > 0
|
||||
AND p.pec_id <> e.pec_id_original;
|
||||
|
||||
-- ========== CONTRÔLE ==========
|
||||
SELECT COUNT(*) AS n_visibles_liste_apres
|
||||
FROM resultats_epreuves_commandees e, resultats_participants p
|
||||
WHERE e.eve_id = @eve
|
||||
AND p.pec_id = e.pec_id_original
|
||||
AND p.par_origine = 'import_fichier'
|
||||
AND p.rol_id NOT IN (3, 4)
|
||||
AND e.is_cancelled = 0
|
||||
AND e.pec_actif = 1;
|
||||
-- Attendu : proche de n_participants_import.
|
||||
56
sql/MSIN-4482-fix-questions-fiche-import.sql
Normal file
56
sql/MSIN-4482-fix-questions-fiche-import.sql
Normal file
@ -0,0 +1,56 @@
|
||||
-- MSIN-4482 — Réparer questions import pour affichage fiche (eve hors inscription en ligne)
|
||||
-- Contexte : réponses en BD mais fiche vide (pec_id / que_actif / que_cart_show)
|
||||
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
|
||||
-- Cible : eve_id 1284 + par_origine = import_fichier seulement (ne touche pas au payant)
|
||||
|
||||
SET @eve := 1284;
|
||||
|
||||
-- Diagnostic : combien de réponses import vs ce que la fiche classique verrait
|
||||
SELECT COUNT(*) AS n_reponses_via_par
|
||||
FROM resultats_questions rq
|
||||
INNER JOIN resultats_participants p
|
||||
ON (rq.par_id = p.par_id OR rq.par_id = p.par_id_original)
|
||||
WHERE p.eve_id = @eve AND p.par_origine = 'import_fichier';
|
||||
|
||||
-- 1) Aligner rq.pec_id sur e.pec_id_original (clé utilisée par la fiche)
|
||||
UPDATE resultats_questions rq
|
||||
INNER JOIN resultats_participants p
|
||||
ON p.eve_id = @eve
|
||||
AND p.par_origine = 'import_fichier'
|
||||
AND (rq.par_id = p.par_id OR rq.par_id = p.par_id_original)
|
||||
INNER JOIN resultats_epreuves_commandees e
|
||||
ON e.eve_id = @eve
|
||||
AND (e.pec_id_original = p.pec_id OR e.pec_id = p.pec_id OR e.pec_id = rq.pec_id)
|
||||
SET rq.pec_id = e.pec_id_original
|
||||
WHERE e.pec_id_original > 0
|
||||
AND rq.pec_id <> e.pec_id_original;
|
||||
|
||||
-- 2) Activer les réponses import
|
||||
UPDATE resultats_questions rq
|
||||
INNER JOIN resultats_participants p
|
||||
ON p.eve_id = @eve
|
||||
AND p.par_origine = 'import_fichier'
|
||||
AND (rq.par_id = p.par_id OR rq.par_id = p.par_id_original)
|
||||
SET rq.que_actif = 1
|
||||
WHERE IFNULL(rq.que_actif, 0) <> 1;
|
||||
|
||||
-- 3) Afficher en fiche (que_cart_show) les questions du catalogue liées à cet import
|
||||
UPDATE inscriptions_questions iq
|
||||
INNER JOIN resultats_questions rq ON rq.que_id = iq.que_id
|
||||
INNER JOIN resultats_participants p
|
||||
ON p.eve_id = @eve
|
||||
AND p.par_origine = 'import_fichier'
|
||||
AND (rq.par_id = p.par_id OR rq.par_id = p.par_id_original)
|
||||
SET iq.que_cart_show = 1,
|
||||
iq.que_cart_label_fr = IF(
|
||||
TRIM(IFNULL(iq.que_cart_label_fr, '')) = '',
|
||||
iq.que_question_fr,
|
||||
iq.que_cart_label_fr
|
||||
),
|
||||
iq.que_cart_label_en = IF(
|
||||
TRIM(IFNULL(iq.que_cart_label_en, '')) = '',
|
||||
IF(TRIM(IFNULL(iq.que_question_en, '')) = '', iq.que_question_fr, iq.que_question_en),
|
||||
iq.que_cart_label_en
|
||||
)
|
||||
WHERE iq.eve_id = @eve
|
||||
AND IFNULL(iq.que_cart_show, 0) <> 1;
|
||||
63
sql/MSIN-4482-import-resultats-templates.sql
Normal file
63
sql/MSIN-4482-import-resultats-templates.sql
Normal 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 — org_id evenement (partage templates même org) ; pas com_id compte',
|
||||
`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;
|
||||
45
sql/MSIN-4482-type-sans-inscription-en-ligne.sql
Normal file
45
sql/MSIN-4482-type-sans-inscription-en-ligne.sql
Normal file
@ -0,0 +1,45 @@
|
||||
-- MSIN-4482 — Type d'événement « Sans inscription en ligne »
|
||||
-- Contexte : marqueur isolé pour eve hors tunnel inscription / paiement MS1 (import resultats_*)
|
||||
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
|
||||
--
|
||||
-- Table : inscriptions_types_evenements
|
||||
-- Lien : inscriptions_evenements.te_id
|
||||
-- Icône : images/te_sans_inscription_en_ligne.png (pas le coureur — écran + interdit)
|
||||
|
||||
-- 1) Créer le type (idempotent)
|
||||
INSERT INTO inscriptions_types_evenements (te_nom_fr, te_nom_en, te_icone, te_actif)
|
||||
SELECT
|
||||
'Sans inscription en ligne',
|
||||
'No online registration',
|
||||
'te_sans_inscription_en_ligne.png',
|
||||
1
|
||||
FROM (SELECT 1 AS _x) AS _seed
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM inscriptions_types_evenements
|
||||
WHERE te_nom_fr = 'Sans inscription en ligne'
|
||||
);
|
||||
|
||||
-- 1b) Si le type existe déjà sans icône (ou à corriger) — petit UPDATE, safe
|
||||
UPDATE inscriptions_types_evenements
|
||||
SET te_icone = 'te_sans_inscription_en_ligne.png'
|
||||
WHERE te_nom_fr = 'Sans inscription en ligne'
|
||||
AND IFNULL(te_icone, '') <> 'te_sans_inscription_en_ligne.png';
|
||||
|
||||
-- 2) Vérification
|
||||
SELECT te_id, te_nom_fr, te_nom_en, te_icone, te_actif
|
||||
FROM inscriptions_types_evenements
|
||||
WHERE te_nom_fr = 'Sans inscription en ligne';
|
||||
|
||||
-- 3) Rattacher l'événement de test (adapter eve_id si besoin)
|
||||
UPDATE inscriptions_evenements
|
||||
SET te_id = (
|
||||
SELECT te_id FROM inscriptions_types_evenements
|
||||
WHERE te_nom_fr = 'Sans inscription en ligne'
|
||||
LIMIT 1
|
||||
)
|
||||
WHERE eve_id = 1284;
|
||||
|
||||
SELECT eve_id, te_id, eve_nom_fr
|
||||
FROM inscriptions_evenements
|
||||
WHERE eve_id = 1284;
|
||||
15
sql/MSIN-4482-urgence-rollback-te-1284.sql
Normal file
15
sql/MSIN-4482-urgence-rollback-te-1284.sql
Normal file
@ -0,0 +1,15 @@
|
||||
-- MSIN-4482 — Urgence : débloquer / rollback type eve 1284 si besoin
|
||||
-- Notes : exécution UNIQUEMENT sur dev préprod
|
||||
--
|
||||
-- 1) Si MySQL est saturé par un gros UPDATE/DELETE import :
|
||||
-- SHOW FULL PROCESSLIST;
|
||||
-- puis KILL <id>; sur les requêtes longues sur resultats_* / inscriptions_questions
|
||||
--
|
||||
-- 2) Remettre l'événement 1284 en type « Course » (te_id typique = 1 — vérifier d'abord) :
|
||||
|
||||
SELECT te_id, te_nom_fr FROM inscriptions_types_evenements WHERE te_actif = 1 ORDER BY te_nom_fr;
|
||||
|
||||
-- Exemple si Course = te_id 1 :
|
||||
-- UPDATE inscriptions_evenements SET te_id = 1 WHERE eve_id = 1284;
|
||||
|
||||
SELECT eve_id, te_id, eve_nom_fr FROM inscriptions_evenements WHERE eve_id = 1284;
|
||||
31
sql/MSIN-4484-perm-grants-all.sql
Normal file
31
sql/MSIN-4484-perm-grants-all.sql
Normal file
@ -0,0 +1,31 @@
|
||||
-- MSIN-4484 — Permission catalogue : hors propagation kit total
|
||||
-- Prérequis : inscriptions_eve_permissions (accès v2)
|
||||
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
|
||||
-- Idempotent
|
||||
--
|
||||
-- perm_grants_all = 1 (défaut) : incluse dans role_grants_all (comportement actuel)
|
||||
-- perm_grants_all = 0 : n’est PAS accordée automatiquement par un kit total ;
|
||||
-- seulement via matrice / extra explicite (ou gate hors catalogue)
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
SET @col_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'inscriptions_eve_permissions'
|
||||
AND COLUMN_NAME = 'perm_grants_all'
|
||||
);
|
||||
|
||||
SET @sql := IF(
|
||||
@col_exists = 0,
|
||||
'ALTER TABLE `inscriptions_eve_permissions`
|
||||
ADD COLUMN `perm_grants_all` tinyint(1) unsigned NOT NULL DEFAULT 1
|
||||
COMMENT ''MSIN-4484 — 1 = incluse dans kit total (grants_all) ; 0 = non propagée''
|
||||
AFTER `perm_actif`',
|
||||
'SELECT ''perm_grants_all already exists'' AS info'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
72
sql/MSIN-4485-dossard-print.sql
Normal file
72
sql/MSIN-4485-dossard-print.sql
Normal file
@ -0,0 +1,72 @@
|
||||
-- MSIN-4485 — Impression dossard PDF (fiche gestion) + droit hors kit total
|
||||
-- Prérequis : inscriptions_eve_permissions (accès v2)
|
||||
-- Inclut la colonne MSIN-4484 (perm_grants_all) si absente — safe si 4484 déjà joué
|
||||
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
|
||||
-- Idempotent
|
||||
--
|
||||
-- Droit non propagé (perm_grants_all = 0) : activer via extra / matrice explicite
|
||||
-- sur le compte voulu — pas via kit owner / total.
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
/* MSIN-4484 — colonne hors propagation kit total (si pas déjà exécuté) */
|
||||
SET @col_exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'inscriptions_eve_permissions'
|
||||
AND COLUMN_NAME = 'perm_grants_all'
|
||||
);
|
||||
|
||||
SET @sql := IF(
|
||||
@col_exists = 0,
|
||||
'ALTER TABLE `inscriptions_eve_permissions`
|
||||
ADD COLUMN `perm_grants_all` tinyint(1) unsigned NOT NULL DEFAULT 1
|
||||
COMMENT ''MSIN-4484 — 1 = incluse dans kit total (grants_all) ; 0 = non propagée''
|
||||
AFTER `perm_actif`',
|
||||
'SELECT ''perm_grants_all already exists'' AS info'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
INSERT INTO `inscriptions_eve_permissions`
|
||||
(`perm_key`, `perm_group`, `perm_scope`, `perm_label_fr`, `perm_label_en`,
|
||||
`perm_description_fr`, `perm_description_en`, `perm_phase`, `perm_actif`,
|
||||
`perm_grants_all`, `perm_tri`)
|
||||
VALUES
|
||||
('inscriptions_gestion.dossard_print', 'inscriptions', 'action',
|
||||
'Gestion inscription : imprimer le dossard (PDF)',
|
||||
'Registration mgmt: print bib (PDF)',
|
||||
'Bouton Imprimer le dossard sur la fiche participant (PDF demi-lettre)',
|
||||
'Print bib button on participant sheet (half-letter PDF)',
|
||||
2, 1, 0, 119)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`perm_group` = VALUES(`perm_group`),
|
||||
`perm_scope` = VALUES(`perm_scope`),
|
||||
`perm_label_fr` = VALUES(`perm_label_fr`),
|
||||
`perm_label_en` = VALUES(`perm_label_en`),
|
||||
`perm_description_fr` = VALUES(`perm_description_fr`),
|
||||
`perm_description_en` = VALUES(`perm_description_en`),
|
||||
`perm_phase` = VALUES(`perm_phase`),
|
||||
`perm_actif` = 1,
|
||||
`perm_grants_all` = 0,
|
||||
`perm_tri` = VALUES(`perm_tri`);
|
||||
|
||||
/* Ne pas laisser ce droit dans un kit par accident (hors propagation). */
|
||||
DELETE FROM `inscriptions_eve_role_permissions`
|
||||
WHERE `perm_key` = 'inscriptions_gestion.dossard_print';
|
||||
|
||||
DELETE FROM info
|
||||
WHERE info_prg = 'compte.php'
|
||||
AND info_clef IN (
|
||||
'inscr_gestion_btn_dossard_print',
|
||||
'inscr_gestion_btn_dossard_print_title'
|
||||
);
|
||||
|
||||
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) VALUES
|
||||
('inscr_gestion_btn_dossard_print', 'fr', 'Imprimer le dossard', '', 'compte.php', 'MSIN-4485', 0, 1, '', '', '', NOW()),
|
||||
('inscr_gestion_btn_dossard_print', 'en', 'Print bib', '', 'compte.php', 'MSIN-4485', 0, 1, '', '', '', NOW()),
|
||||
('inscr_gestion_btn_dossard_print_title', 'fr', 'Générer le PDF du dossard', '', 'compte.php', 'MSIN-4485', 0, 1, '', '', '', NOW()),
|
||||
('inscr_gestion_btn_dossard_print_title', 'en', 'Generate bib PDF', '', 'compte.php', 'MSIN-4485', 0, 1, '', '', '', NOW());
|
||||
134
superadm/ajax_import_resultats.php
Normal file
134
superadm/ajax_import_resultats.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* MSIN-4482 — AJAX import participants (hors transaction) — superadm.
|
||||
* Buffer strict : toute sortie parasite (erreur SQL, notices Excel) casse dataType:json.
|
||||
*/
|
||||
ob_start();
|
||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE & ~E_WARNING);
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
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');
|
||||
|
||||
// MSIN-4482 — toutes les actions mutantes exigent le type « Sans inscription en ligne »
|
||||
$tabActionsGuard = array('upload', 'save_template', 'validate', 'commit', 'reset_preview', 'reset');
|
||||
if (in_array($strAction, $tabActionsGuard, true)) {
|
||||
$tabGuard = fxImportResultatsGuardEveSansInscription($intEveId);
|
||||
if (is_array($tabGuard)) {
|
||||
$tabRetour = $tabGuard;
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
if (!headers_sent()) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
}
|
||||
echo json_encode($tabRetour, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
case 'commit':
|
||||
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 = fxImportResultatsCommit($intEveId, $strToken, $arrMapping);
|
||||
break;
|
||||
|
||||
case 'reset_preview':
|
||||
if ($intEveId <= 0) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||
break;
|
||||
}
|
||||
$tabRetour = fxImportResultatsResetPreview($intEveId);
|
||||
break;
|
||||
|
||||
case 'reset':
|
||||
if ($intEveId <= 0) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||
break;
|
||||
}
|
||||
$strConfirm = trim((string)($_REQUEST['confirm'] ?? ''));
|
||||
$tabRetour = fxImportResultatsReset($intEveId, $strConfirm);
|
||||
break;
|
||||
|
||||
default:
|
||||
$tabRetour = array('state' => 'error', 'message' => 'action_invalide');
|
||||
break;
|
||||
}
|
||||
|
||||
// Jeter tout HTML parasite (erreur SQL legacy, notices PHPExcel, etc.)
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
if (!headers_sent()) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
}
|
||||
echo json_encode($tabRetour, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -978,7 +978,8 @@ function fxEveAccesAdminRenderKitForm($intRoleId, $strError = '')
|
||||
</label>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
Si coché : aucune matrice à maintenir. Sinon : choisissez les droits un par un ci-dessous.
|
||||
Si coché : aucune matrice à maintenir (sauf clés marquées
|
||||
<em>hors kit total</em>, MSIN-4484). Sinon : choisissez les droits un par un ci-dessous.
|
||||
</small>
|
||||
</div>
|
||||
<?php } ?>
|
||||
@ -1014,6 +1015,8 @@ function fxEveAccesAdminRenderKitForm($intRoleId, $strError = '')
|
||||
<strong>Niveau 1</strong> = accès aux menus du hub.
|
||||
<strong>Niveau 2</strong> = actions dans la page (ex. Inscriptions V2).
|
||||
La page <code>/mobile</code> est la même gestion inscriptions en affichage mobile — pas une API.
|
||||
Badge <span class="badge badge-warning">hors kit total</span> = non inclus dans un kit total
|
||||
(MSIN-4484) ; cocher ici seulement pour une attribution explicite.
|
||||
</p>
|
||||
<?php
|
||||
foreach ($arrOrderedGroups as $strGroup => $arrPerms) {
|
||||
@ -1033,16 +1036,23 @@ function fxEveAccesAdminRenderKitForm($intRoleId, $strError = '')
|
||||
$strKey = $rowPerm['perm_key'];
|
||||
$strId = 'perm_' . preg_replace('/[^a-z0-9_]/', '_', $strKey);
|
||||
$strScope = (string)($rowPerm['perm_scope'] ?? '');
|
||||
$blnPropagates = !isset($rowPerm['perm_grants_all'])
|
||||
|| intval($rowPerm['perm_grants_all']) === 1;
|
||||
$strBadge = ($strScope === 'page')
|
||||
? '<span class="badge badge-primary ml-1">page</span>'
|
||||
: (($strScope === 'action')
|
||||
? '<span class="badge badge-secondary ml-1">détail</span>'
|
||||
: '');
|
||||
if (!$blnPropagates) {
|
||||
$strBadge .= ' <span class="badge badge-warning ml-1">hors kit total</span>';
|
||||
}
|
||||
$strPad = ($strScope === 'action') ? ' pl-3' : '';
|
||||
$strChkClass = 'custom-control-input perm-checkbox'
|
||||
. ($blnPropagates ? '' : ' perm-checkbox-no-grants-all');
|
||||
?>
|
||||
<div class="col-md-6 col-lg-4 mb-2<?= $strPad ?>">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input perm-checkbox"
|
||||
<input type="checkbox" class="<?= htmlspecialchars($strChkClass, ENT_QUOTES, 'UTF-8') ?>"
|
||||
id="<?= htmlspecialchars($strId, ENT_QUOTES, 'UTF-8') ?>"
|
||||
name="perm_keys[]"
|
||||
value="<?= htmlspecialchars($strKey, ENT_QUOTES, 'UTF-8') ?>"
|
||||
@ -1116,7 +1126,8 @@ function fxEveAccesAdminRenderKitForm($intRoleId, $strError = '')
|
||||
syncGrantsAllUi();
|
||||
|
||||
$('#btn-perm-all').on('click', function () {
|
||||
$('.perm-checkbox').prop('checked', true);
|
||||
// MSIN-4484 — ne pas cocher les clés hors kit total (évite assignation accidentelle).
|
||||
$('.perm-checkbox').not('.perm-checkbox-no-grants-all').prop('checked', true);
|
||||
});
|
||||
$('#btn-perm-none').on('click', function () {
|
||||
$('.perm-checkbox').prop('checked', false);
|
||||
@ -1124,7 +1135,7 @@ function fxEveAccesAdminRenderKitForm($intRoleId, $strError = '')
|
||||
$('.perm-group-block').each(function () {
|
||||
var $block = $(this);
|
||||
$block.find('.btn-group-all').on('click', function () {
|
||||
$block.find('.perm-checkbox').prop('checked', true);
|
||||
$block.find('.perm-checkbox').not('.perm-checkbox-no-grants-all').prop('checked', true);
|
||||
});
|
||||
$block.find('.btn-group-none').on('click', function () {
|
||||
$block.find('.perm-checkbox').prop('checked', false);
|
||||
|
||||
Reference in New Issue
Block a user