145 lines
7.1 KiB
JavaScript
145 lines
7.1 KiB
JavaScript
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
||
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||
|
|
|
||
|
|
// Les bilans du mobilier (§ 6.1, § 6.2, § 2.1 étape 5) : ce qu'un changement
|
||
|
|
// de défaut, de tours ou la suppression d'une table emporterait, compté sur
|
||
|
|
// la charge courante sans rien écrire, pour l'énoncer avant d'appliquer. La
|
||
|
|
// commande qui applique lit le même bilan : changerDefaut lève le premier de
|
||
|
|
// ses refus. Les refus et avertissements d'une capacité sont ceux de
|
||
|
|
// controlerCapacites (capacite.js), le seul contrôle. Aucune fonction ne
|
||
|
|
// modifie ce qu'elle reçoit.
|
||
|
|
import { ordreDesReservations } from '../csv/import.js';
|
||
|
|
import { capacite } from '../stockage/document.js';
|
||
|
|
import { controlerCapacites } from './capacite.js';
|
||
|
|
import { presents } from './places.js';
|
||
|
|
|
||
|
|
const croissant = (a, b) => a - b;
|
||
|
|
const comparerTextes = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
||
|
|
const parNumero = (a, b) => a.numero - b.numero || a.id - b.id;
|
||
|
|
|
||
|
|
// Un nombre entier attendu ; sa lecture depuis une saisie appartient à
|
||
|
|
// lireEntier.
|
||
|
|
function exigerEntier(valeur, nom) {
|
||
|
|
if (!Number.isSafeInteger(valeur)) throw new TypeError(`${nom} : entier attendu, reçu ${JSON.stringify(valeur)}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// L'enregistrement de la table d'identifiant id, ou TypeError.
|
||
|
|
function tableDe(charge, id) {
|
||
|
|
const table = charge.tables.find((candidate) => candidate.id === id);
|
||
|
|
if (table === undefined) throw new TypeError(`table inconnue : ${JSON.stringify(id)}`);
|
||
|
|
return table;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Les identifiants croissants des placements de la liste qui satisfont
|
||
|
|
// touche ; un placement fautif, de forme quelconque, se lit sans lever.
|
||
|
|
const identifiants = (placements, touche) =>
|
||
|
|
placements.filter((placement) => touche(placement)).map(({ id }) => id).sort(croissant);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Le changement du défaut à sieges places (§ 6.1), énoncé avant d'être
|
||
|
|
* appliqué : suivent, le nombre de tables qui suivent le défaut ; avant et
|
||
|
|
* apres, le défaut d'aujourd'hui et sieges ; surchargees, les capacités des
|
||
|
|
* tables surchargées par numéro de table ; totalAvant et totalApres, la
|
||
|
|
* somme des capacités avant et après ; participants, les personnes non
|
||
|
|
* exclues. realigner : les surchargées passent aussi à sieges.
|
||
|
|
*
|
||
|
|
* Chaque table dont la capacité change se contrôle comme une saisie au champ
|
||
|
|
* (§ 6.2) : refus, le premier de chaque table qui refuse, {table, code,
|
||
|
|
* details}, par ordre de charge.tables ; avertissements, de même, ceux des
|
||
|
|
* autres — SURNUMERAIRES, AU_DELA_LISIBLE. Moins de 2 places refusent pour
|
||
|
|
* l'événement entier : un seul refus, MOINS_DE_DEUX, table null.
|
||
|
|
*
|
||
|
|
* @param {import('../stockage/types.js').Charge} charge
|
||
|
|
* @param {number} sieges
|
||
|
|
* @param {{realigner?: boolean}} [options]
|
||
|
|
* @returns {{suivent: number, avant: number, apres: number, surchargees: number[], totalAvant: number,
|
||
|
|
* totalApres: number, participants: number, refus: Array<{table: number|null, code: string, details: Object}>,
|
||
|
|
* avertissements: Array<{table: number, code: string, details: Object}>}}
|
||
|
|
* @throws {TypeError} sieges qui n'est pas un entier
|
||
|
|
*/
|
||
|
|
export function bilanChangementDefaut(charge, sieges, { realigner = false } = {}) {
|
||
|
|
exigerEntier(sieges, 'sieges');
|
||
|
|
const apresDe = (table) => (table.sieges === null || realigner ? sieges : table.sieges);
|
||
|
|
const somme = (valeurs) => valeurs.reduce((total, valeur) => total + valeur, 0);
|
||
|
|
const changements = new Map(
|
||
|
|
charge.tables.filter((table) => apresDe(table) !== capacite(charge, table)).map((table) => [table.id, apresDe(table)]),
|
||
|
|
);
|
||
|
|
const controle =
|
||
|
|
sieges < 2
|
||
|
|
? { refus: [{ table: null, code: 'MOINS_DE_DEUX', details: { remede: null } }], avertissements: [] }
|
||
|
|
: controlerCapacites(charge, changements, { origine: 'champ' });
|
||
|
|
return {
|
||
|
|
suivent: charge.tables.filter((table) => table.sieges === null).length,
|
||
|
|
avant: charge.evenement.siegesParDefaut,
|
||
|
|
apres: sieges,
|
||
|
|
surchargees: charge.tables
|
||
|
|
.filter((table) => table.sieges !== null)
|
||
|
|
.sort(parNumero)
|
||
|
|
.map((table) => table.sieges),
|
||
|
|
totalAvant: somme(charge.tables.map((table) => capacite(charge, table))),
|
||
|
|
totalApres: somme(charge.tables.map(apresDe)),
|
||
|
|
participants: presents(charge).size,
|
||
|
|
refus: controle.refus,
|
||
|
|
avertissements: controle.avertissements,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Le changement du nombre de tours à tours (§ 2.1 étape 5, § 4.3) :
|
||
|
|
* reservationsDetruites, les réservations « tour désigné » au-delà, dans
|
||
|
|
* l'ordre canonique ; propositionsTouchees, les identifiants croissants des
|
||
|
|
* propositions qui ne porteront pas tours tours, et retenuTouche de même
|
||
|
|
* pour le retenu — ils restent, en dérive TOURS_CHANGES (§ 9). Le nombre de
|
||
|
|
* tours d'aujourd'hui ne touche rien. La portée « tous » ne se détruit
|
||
|
|
* jamais : elle n'énumère aucun tour.
|
||
|
|
*
|
||
|
|
* @param {import('../stockage/types.js').Charge} charge
|
||
|
|
* @param {number} tours
|
||
|
|
* @returns {{reservationsDetruites: Array<{participant: number, table: number, tour: number}>,
|
||
|
|
* propositionsTouchees: number[], retenuTouche: boolean}}
|
||
|
|
* @throws {TypeError} tours qui n'est pas un entier
|
||
|
|
*/
|
||
|
|
export function bilanTours(charge, tours) {
|
||
|
|
exigerEntier(tours, 'tours');
|
||
|
|
const change = tours !== charge.evenement.tours;
|
||
|
|
const touche = (placement) => change && placement?.tours !== tours;
|
||
|
|
return {
|
||
|
|
reservationsDetruites: charge.reservations
|
||
|
|
.filter(({ portee, tour }) => portee === 'tour' && tour > tours)
|
||
|
|
.sort(ordreDesReservations)
|
||
|
|
.map(({ participant, table, tour }) => ({ participant, table, tour })),
|
||
|
|
propositionsTouchees: identifiants(charge.propositions, touche),
|
||
|
|
retenuTouche: charge.retenu !== null && touche(charge.retenu),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* La suppression de la table id (§ 6.2, § 4) : ses réservations, suspendues
|
||
|
|
* comprises, dans l'ordre canonique, et ses titres par siège puis libellé,
|
||
|
|
* qui partent avec elle ; propositionsTouchees, les identifiants croissants
|
||
|
|
* des propositions qui la déclarent, et retenuTouche de même — ils restent,
|
||
|
|
* en dérive TABLE_SUPPRIMEE (§ 9). Une table nue rend deux listes vides.
|
||
|
|
*
|
||
|
|
* @param {import('../stockage/types.js').Charge} charge
|
||
|
|
* @param {number} id
|
||
|
|
* @returns {{reservations: Array<{participant: number, portee: 'tous'|'tour', tour: number|null, siege: number|null}>,
|
||
|
|
* titres: Array<{siege: number, libelle: string}>, propositionsTouchees: number[], retenuTouche: boolean}}
|
||
|
|
* @throws {TypeError} une table inconnue
|
||
|
|
*/
|
||
|
|
export function bilanSuppressionTable(charge, id) {
|
||
|
|
tableDe(charge, id);
|
||
|
|
const declare = (placement) => Array.isArray(placement?.tables) && placement.tables.includes(id);
|
||
|
|
return {
|
||
|
|
reservations: charge.reservations
|
||
|
|
.filter(({ table }) => table === id)
|
||
|
|
.sort(ordreDesReservations)
|
||
|
|
.map(({ participant, portee, tour, siege }) => ({ participant, portee, tour, siege })),
|
||
|
|
titres: charge.titres
|
||
|
|
.filter(({ table }) => table === id)
|
||
|
|
.sort((a, b) => a.siege - b.siege || comparerTextes(a.libelle, b.libelle))
|
||
|
|
.map(({ siege, libelle }) => ({ siege, libelle })),
|
||
|
|
propositionsTouchees: identifiants(charge.propositions, declare),
|
||
|
|
retenuTouche: charge.retenu !== null && declare(charge.retenu),
|
||
|
|
};
|
||
|
|
}
|