[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
|
|
// Épreuves des bilans du mobilier (§ 6.1, § 6.2, § 2.1 étape 5, § 4.3) : ce
|
|
|
|
|
|
// qu'un changement de défaut, de tours ou une suppression de table emporte,
|
|
|
|
|
|
// énoncé avant d'appliquer, sans rien écrire. Les noms d'épreuve sont
|
|
|
|
|
|
// inventés.
|
|
|
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
|
|
import { describe, test } from '../../test/lanceur.js';
|
|
|
|
|
|
import { nombrePlaces, normaliser } from '../moteur/configuration.js';
|
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
|
|
|
|
import { SIEGES_MAX, configurationDepuisCharge, creerCharge } from '../stockage/document.js';
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
import { bilanChangementDefaut, bilanSuppressionTable, bilanTours } from './bilans.js';
|
|
|
|
|
|
import { COMMANDES } from './commandes.js';
|
|
|
|
|
|
|
|
|
|
|
|
const NOMS = ['Ombrelle', 'Pervenche', 'Lacasse', 'Grisaille', 'Bruyère', 'Sarcelle', 'Ancolie', 'Mélèze', 'Quenouille', 'Pivoine'];
|
|
|
|
|
|
|
|
|
|
|
|
const personne = (id, autres = {}) => ({
|
|
|
|
|
|
id,
|
|
|
|
|
|
nom: NOMS[(id - 1) % NOMS.length],
|
|
|
|
|
|
prenom: id === 2 ? 'Théo' : null,
|
|
|
|
|
|
appartenance: null,
|
|
|
|
|
|
courriel: null,
|
|
|
|
|
|
titrePressenti: null,
|
|
|
|
|
|
notes: null,
|
|
|
|
|
|
exclu: false,
|
|
|
|
|
|
...autres,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const resa = (participant, table, siege, tour = null) => ({ participant, table, siege, portee: tour === null ? 'tous' : 'tour', tour });
|
|
|
|
|
|
|
|
|
|
|
|
// Une charge : les tables données ({id, sieges}, numéro égal à l'identifiant),
|
|
|
|
|
|
// n personnes dont les exclus, R tours, un défaut.
|
|
|
|
|
|
function chargeDe({ tables, n, exclus = [], tours = 2, defaut = 8, attribuerSieges = false, reservations = [], titres = [] }) {
|
|
|
|
|
|
const charge = creerCharge({ id: 'evt-bilans', nom: 'Soirée des Merles', siegesParDefaut: defaut, tours });
|
|
|
|
|
|
charge.reglages.attribuerSieges = attribuerSieges;
|
|
|
|
|
|
charge.participants = Array.from({ length: n }, (_, rang) => personne(rang + 1, { exclu: exclus.includes(rang + 1) }));
|
|
|
|
|
|
charge.tables = tables.map(({ id, sieges = null }) => ({ id, numero: id, sieges, forme: 'ronde', position: { x: 300 * id, y: 0 } }));
|
|
|
|
|
|
charge.reservations = reservations;
|
|
|
|
|
|
charge.titres = titres;
|
|
|
|
|
|
charge.prochainsIds = { participant: n + 1, table: Math.max(0, ...tables.map(({ id }) => id)) + 1, proposition: 1 };
|
|
|
|
|
|
return charge;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ajoute la proposition id, dont les listes de chaque tour suivent l'ordre
|
|
|
|
|
|
// de charge.tables, et la retient quand retenu est vrai.
|
|
|
|
|
|
function placer(charge, listes, { id, retenu = false }) {
|
|
|
|
|
|
const participants = charge.participants.filter(({ exclu }) => !exclu).map(({ id: p }) => p);
|
|
|
|
|
|
const plan = {
|
|
|
|
|
|
siegesAttribues: charge.reglages.attribuerSieges,
|
|
|
|
|
|
tables: charge.tables.map(({ id: t }) => t),
|
|
|
|
|
|
capacites: charge.tables.map(({ sieges }) => sieges ?? charge.evenement.siegesParDefaut),
|
|
|
|
|
|
tours: listes.length,
|
|
|
|
|
|
participants,
|
|
|
|
|
|
placement: listes.map((tour) => ({ sieges: tour, reserve: participants.filter((p) => !tour.flat().includes(p)) })),
|
|
|
|
|
|
};
|
|
|
|
|
|
charge.propositions.push({ id, graine: id, arret: 40, historique: 1000, produitVersion: 'essai', ...plan });
|
|
|
|
|
|
charge.prochainsIds.proposition = id + 1;
|
|
|
|
|
|
if (retenu) {
|
|
|
|
|
|
charge.retenu = { proposition: id, ...structuredClone(plan) };
|
|
|
|
|
|
charge.evenement.etat = 'retenu';
|
|
|
|
|
|
} else if (charge.evenement.etat === 'brouillon') {
|
|
|
|
|
|
charge.evenement.etat = 'propose';
|
|
|
|
|
|
}
|
|
|
|
|
|
return charge;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const afficherTour = (tour) => ({ geste: 'afficherTour', tour, proposition: null });
|
|
|
|
|
|
|
|
|
|
|
|
// Treize tables : neuf suivent 8, les tables 3, 6, 9 et 12 sont surchargées à
|
|
|
|
|
|
// 7, 7, 6 et 12 ; 120 personnes, dont 2 exclues.
|
|
|
|
|
|
function treizeTables(options = {}) {
|
|
|
|
|
|
const surcharges = new Map([[3, 7], [6, 7], [9, 6], [12, 12]]);
|
|
|
|
|
|
const tables = Array.from({ length: 13 }, (_, rang) => ({ id: rang + 1, sieges: surcharges.get(rang + 1) ?? null }));
|
|
|
|
|
|
return chargeDe({ tables, n: 120, exclus: [40, 80], ...options });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
describe('bilans : changer le défaut (§ 6.1)', () => {
|
|
|
|
|
|
test("l'énoncé chiffré du § 6.1 : 9 tables passent de 8 à 10, 4 surchargées ne changent pas, 104 → 122 pour 118", () => {
|
|
|
|
|
|
const charge = treizeTables();
|
|
|
|
|
|
const bilan = bilanChangementDefaut(charge, 10);
|
|
|
|
|
|
assert.deepEqual(bilan, {
|
|
|
|
|
|
suivent: 9,
|
|
|
|
|
|
avant: 8,
|
|
|
|
|
|
apres: 10,
|
|
|
|
|
|
surchargees: [7, 7, 6, 12],
|
|
|
|
|
|
totalAvant: 104,
|
|
|
|
|
|
totalApres: 122,
|
|
|
|
|
|
participants: 118,
|
|
|
|
|
|
refus: [],
|
|
|
|
|
|
avertissements: [],
|
|
|
|
|
|
});
|
|
|
|
|
|
// Le total et les participants sont ceux de l'instance de la charge appliquée.
|
|
|
|
|
|
const apres = COMMANDES.get('changerDefaut')(charge, { sieges: 10, realigner: false }).charge;
|
|
|
|
|
|
const instance = normaliser(configurationDepuisCharge(apres));
|
|
|
|
|
|
assert.equal(bilan.totalApres, nombrePlaces(instance));
|
|
|
|
|
|
assert.equal(bilan.participants, instance.N);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('réaligner : les surchargées changent aussi, et leurs refus en sont', () => {
|
|
|
|
|
|
const charge = treizeTables({ attribuerSieges: true, titres: [{ table: 12, siege: 12, libelle: 'animateur' }] });
|
|
|
|
|
|
const realigne = bilanChangementDefaut(charge, 10, { realigner: true });
|
|
|
|
|
|
assert.equal(realigne.totalApres, 130);
|
|
|
|
|
|
assert.deepEqual(realigne.refus, [
|
|
|
|
|
|
{
|
|
|
|
|
|
table: 12,
|
|
|
|
|
|
code: 'SIEGES_RETIRES',
|
|
|
|
|
|
details: {
|
|
|
|
|
|
table: 12,
|
|
|
|
|
|
sieges: [{ siege: 12, cause: 'SIEGE_TITRE', libelle: 'animateur' }],
|
|
|
|
|
|
remede: { commande: 'retirerTitre', arguments: { table: 12, siege: 12 } },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
assert.deepEqual(bilanChangementDefaut(charge, 10).refus, []);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('une baisse se contrôle table par table : la table 2, sous ses 7 réservations, refuse ; les autres non', () => {
|
|
|
|
|
|
const reservations = [1, 2, 3, 4, 5, 6, 7].map((id) => resa(id, 2, null));
|
|
|
|
|
|
const charge = chargeDe({ tables: [{ id: 1 }, { id: 2 }, { id: 3 }], n: 20, reservations });
|
|
|
|
|
|
const bilan = bilanChangementDefaut(charge, 6);
|
|
|
|
|
|
assert.deepEqual(bilan.refus, [
|
|
|
|
|
|
{ table: 2, code: 'SOUS_RESERVATIONS', details: { table: 2, n: 7, tour: 1, remede: afficherTour(1) } },
|
|
|
|
|
|
]);
|
|
|
|
|
|
assert.equal(bilan.totalApres, 18);
|
|
|
|
|
|
assert.deepEqual(bilanChangementDefaut(charge, 1).refus, [{ table: null, code: 'MOINS_DE_DEUX', details: { remede: null } }]);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
[FIX] iteration 3 review: seat targets, gestures, warnings, settings
The wheel no longer zooms during a gesture, so a drop lands under the
ghost. A seat is a target the size of its pitch. A floor drop refuses to
remove an every-tour reservation. A seat reservation the retained plan
does not hold is flagged at the seat, with its swap as remedy. DERIVE names
the person; capacities have an upper bound; Generate waits for a pending
setting; a worker that fails to load shows a refusal; § 5.3 constraints
get their switches. Commands are not re-entrant and a double click does not
confirm. Checked: 2755 node, 550 browser tests, from the index alone.
--- FR ---
[FIX] revue de l'itération 3 : cibles, gestes, avertissements, réglages
La molette ne zoome plus pendant un geste : le dépôt tombe sous le fantôme.
Un siège est une cible de la taille de son pas. Un dépôt sur le sol refuse
de retirer une réservation de tous les tours. Une réservation à un siège
que le retenu ne tient pas est relevée au siège, l'échange en remède. La
dérive nomme la personne ; les capacités ont une borne ; Générer attend un
réglage en cours ; un travailleur qui ne se charge pas donne un refus ; le
§ 5.3 a ses bascules. Les commandes ne sont pas ré-entrantes et un double
clic ne confirme pas. Vérifié : 2755 node, 550 navigateur, index seul.
Assisted-by: Claude Opus 5.5
2026-10-07 17:50:35 -04:00
|
|
|
|
test("au-delà de SIEGES_MAX, un seul refus pour l'événement, TROP_DE_PLACES, même quand aucune table ne suit le défaut", () => {
|
|
|
|
|
|
const refus = [{ table: null, code: 'TROP_DE_PLACES', details: { max: SIEGES_MAX, remede: null } }];
|
|
|
|
|
|
const suivent = chargeDe({ tables: [{ id: 1 }, { id: 2 }], n: 4 });
|
|
|
|
|
|
assert.deepEqual(bilanChangementDefaut(suivent, SIEGES_MAX + 1).refus, refus);
|
|
|
|
|
|
const surchargees = chargeDe({ tables: [{ id: 1, sieges: 6 }], n: 4 });
|
|
|
|
|
|
assert.deepEqual(bilanChangementDefaut(surchargees, SIEGES_MAX + 1).refus, refus);
|
|
|
|
|
|
assert.deepEqual(bilanChangementDefaut(suivent, SIEGES_MAX).refus, []);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
test('une baisse sous un retenu occupé avertit SURNUMERAIRES : deux personnes de la table 3 au tour 2', () => {
|
|
|
|
|
|
const charge = chargeDe({ tables: [{ id: 1 }, { id: 2 }, { id: 3 }], n: 18 });
|
|
|
|
|
|
const suite = (de, a) => Array.from({ length: a - de + 1 }, (_, rang) => de + rang);
|
|
|
|
|
|
placer(
|
|
|
|
|
|
charge,
|
|
|
|
|
|
[
|
|
|
|
|
|
[suite(1, 6), suite(7, 12), suite(13, 18)],
|
|
|
|
|
|
[suite(1, 5), suite(6, 10), suite(11, 18)],
|
|
|
|
|
|
],
|
|
|
|
|
|
{ id: 1, retenu: true },
|
|
|
|
|
|
);
|
|
|
|
|
|
const bilan = bilanChangementDefaut(charge, 6);
|
|
|
|
|
|
assert.deepEqual(bilan.refus, []);
|
|
|
|
|
|
assert.deepEqual(bilan.avertissements, [
|
|
|
|
|
|
{ table: 3, code: 'SURNUMERAIRES', details: { table: 3, tour: 2, personnes: [17, 18] } },
|
|
|
|
|
|
]);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("le bilan n'écrit rien : la charge reçue, gelée, ne change pas", () => {
|
|
|
|
|
|
const charge = Object.freeze(treizeTables());
|
|
|
|
|
|
assert.equal(bilanChangementDefaut(charge, 6).totalApres, 9 * 6 + 32);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('bilans : changer les tours (§ 2.1 étape 5, § 4.3)', () => {
|
|
|
|
|
|
// R = 4 : deux réservations au tour 4, une au tour 2, une « tous » ; deux
|
|
|
|
|
|
// propositions et un retenu.
|
|
|
|
|
|
function quatreTours() {
|
|
|
|
|
|
const reservations = [resa(1, 1, null, 4), resa(2, 2, null, 4), resa(3, 1, null, 2), resa(4, 2, null)];
|
|
|
|
|
|
const charge = chargeDe({ tables: [{ id: 1, sieges: 4 }, { id: 2, sieges: 4 }], n: 8, tours: 4, reservations });
|
|
|
|
|
|
const tour = [[1, 2, 3, 5], [4, 6, 7, 8]];
|
|
|
|
|
|
const listes = [tour, [[1, 3, 5, 6], [2, 4, 7, 8]], [[1, 5, 6, 7], [2, 3, 4, 8]], [[1, 3, 5, 7], [2, 4, 6, 8]]];
|
|
|
|
|
|
placer(charge, listes, { id: 1 });
|
|
|
|
|
|
placer(charge, listes, { id: 2, retenu: true });
|
|
|
|
|
|
return charge;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test('retirer le tour 4 détruit ses deux réservations « tour » ; propositions et retenu touchés', () => {
|
|
|
|
|
|
const charge = quatreTours();
|
|
|
|
|
|
assert.deepEqual(bilanTours(charge, 3), {
|
|
|
|
|
|
reservationsDetruites: [
|
|
|
|
|
|
{ participant: 1, table: 1, tour: 4 },
|
|
|
|
|
|
{ participant: 2, table: 2, tour: 4 },
|
|
|
|
|
|
],
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
objectifsDetruits: [],
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
propositionsTouchees: [1, 2],
|
|
|
|
|
|
retenuTouche: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("le même nombre ne touche rien ; en ajouter ne détruit rien, mais périme les placements", () => {
|
|
|
|
|
|
const charge = quatreTours();
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
const rien = { reservationsDetruites: [], objectifsDetruits: [] };
|
|
|
|
|
|
assert.deepEqual(bilanTours(charge, 4), { ...rien, propositionsTouchees: [], retenuTouche: false });
|
|
|
|
|
|
assert.deepEqual(bilanTours(charge, 5), { ...rien, propositionsTouchees: [1, 2], retenuTouche: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('retirer les tours 3 et 4 compte les objectifs de ces tours, par table puis tour, quel que soit l’ordre de la charge ; la soirée reste', () => {
|
|
|
|
|
|
const charge = quatreTours();
|
|
|
|
|
|
const objectif = (tour) => ({ portee: tour === null ? 'tous' : 'tour', tour, regle: null, etiquettes: ['finance'] });
|
|
|
|
|
|
charge.tables[1].objectifs = [objectif(null), objectif(2), objectif(3), objectif(4)];
|
|
|
|
|
|
charge.tables[0].objectifs = [objectif(4)];
|
|
|
|
|
|
charge.tables.reverse();
|
|
|
|
|
|
assert.deepEqual(bilanTours(charge, 2).objectifsDetruits, [
|
|
|
|
|
|
{ table: 1, tour: 4 },
|
|
|
|
|
|
{ table: 2, tour: 3 },
|
|
|
|
|
|
{ table: 2, tour: 4 },
|
|
|
|
|
|
]);
|
|
|
|
|
|
assert.deepEqual(bilanTours(charge, 4).objectifsDetruits, []);
|
|
|
|
|
|
// La commande détruit ce que le bilan annonce.
|
|
|
|
|
|
assert.equal(COMMANDES.get('changerTours')(charge, { tours: 2 }).libelle, 'Nombre de tours : 2 ; 2 réservations retirées, 3 objectifs retirés');
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("bilans : supprimer une table (§ 6.2, § 4)", () => {
|
|
|
|
|
|
function garnie() {
|
|
|
|
|
|
const charge = chargeDe({
|
|
|
|
|
|
tables: [{ id: 1 }, { id: 2 }, { id: 3 }],
|
|
|
|
|
|
n: 6,
|
|
|
|
|
|
reservations: [resa(2, 2, 2), resa(5, 1, null)],
|
|
|
|
|
|
titres: [{ table: 2, siege: 2, libelle: 'animateur' }, { table: 1, siege: 1, libelle: 'accueil' }],
|
|
|
|
|
|
});
|
|
|
|
|
|
const listes = [[[1, 5], [2, 3], [4, 6]], [[3, 5], [2, 6], [1, 4]]];
|
|
|
|
|
|
placer(charge, listes, { id: 1 });
|
|
|
|
|
|
placer(charge, listes, { id: 2, retenu: true });
|
|
|
|
|
|
return charge;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test('la table 2 porte la réservation « tous » de Théo Pervenche et un titre : le bilan les nomme, et les placements touchés', () => {
|
|
|
|
|
|
assert.deepEqual(bilanSuppressionTable(garnie(), 2), {
|
|
|
|
|
|
reservations: [{ participant: 2, portee: 'tous', tour: null, siege: 2 }],
|
|
|
|
|
|
titres: [{ siege: 2, libelle: 'animateur' }],
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
objectifs: [],
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
propositionsTouchees: [1, 2],
|
|
|
|
|
|
retenuTouche: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
test("les objectifs de la table partent avec elle : le bilan les nomme, la soirée d'abord, puis les tours", () => {
|
|
|
|
|
|
const charge = garnie();
|
|
|
|
|
|
charge.tables[1].objectifs = [
|
|
|
|
|
|
{ portee: 'tour', tour: 2, regle: 'et', etiquettes: ['Montréal', 'finance'] },
|
|
|
|
|
|
{ portee: 'tous', tour: null, regle: null, etiquettes: ['finance'] },
|
|
|
|
|
|
];
|
|
|
|
|
|
assert.deepEqual(bilanSuppressionTable(charge, 2).objectifs, [
|
|
|
|
|
|
{ tour: null, regle: null, etiquettes: ['finance'] },
|
|
|
|
|
|
{ tour: 2, regle: 'et', etiquettes: ['Montréal', 'finance'] },
|
|
|
|
|
|
]);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
test('une table nue : deux listes vides ; une table créée après les placements ne les touche pas', () => {
|
|
|
|
|
|
const charge = garnie();
|
|
|
|
|
|
const nue = bilanSuppressionTable(charge, 3);
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
assert.deepEqual([nue.reservations, nue.titres, nue.objectifs], [[], [], []]);
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
const creee = COMMANDES.get('creerTable')(charge, { forme: 'carree' }).charge;
|
|
|
|
|
|
const idCree = creee.tables.at(-1).id;
|
|
|
|
|
|
// creerTable fait entrer la table au retenu ; aucune proposition ne la déclare.
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
assert.deepEqual(bilanSuppressionTable(creee, idCree), {
|
|
|
|
|
|
reservations: [],
|
|
|
|
|
|
titres: [],
|
|
|
|
|
|
objectifs: [],
|
|
|
|
|
|
propositionsTouchees: [],
|
|
|
|
|
|
retenuTouche: true,
|
|
|
|
|
|
});
|
[ADD] application: furniture — tables, default, tours, capacity checks
Changing a table, the default seat count or the number of tours reshapes
a plan the operator may already have retained. Eight commands create,
remove and resize tables and set the default and the tours; every change
of capacity first passes controlerCapacite, which refuses to cut a reserved
or titled seat, states who goes to the reserve, and only brings back a
reserved person the display showed seated (§ 6.1, § 6.2). bilans.js states
the figures of a change before it applies.
Checked: 2033 node and 127 browser tests.
--- FR ---
[ADD] application : mobilier — tables, défaut, tours, contrôles
Changer une table, le défaut de sièges ou le nombre de tours remodèle un
plan que l'opérateur a peut-être retenu. Huit commandes créent, retirent et
redimensionnent les tables, et règlent le défaut et les tours ; tout
changement de capacité passe d'abord par controlerCapacite, qui refuse de
couper un siège réservé ou titré, dit qui part en réserve, et ne ramène une
personne réservée que si l'écran la montrait assise (§ 6.1, § 6.2).
bilans.js énonce les chiffres d'un changement avant qu'il s'applique.
Vérifié : 2033 node et 127 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 05:12:43 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('les titres se rangent par siège puis libellé', () => {
|
|
|
|
|
|
const charge = garnie();
|
|
|
|
|
|
charge.titres.push({ table: 2, siege: 1, libelle: 'hôte' }, { table: 2, siege: 2, libelle: 'accueil' });
|
|
|
|
|
|
assert.deepEqual(bilanSuppressionTable(charge, 2).titres, [
|
|
|
|
|
|
{ siege: 1, libelle: 'hôte' },
|
|
|
|
|
|
{ siege: 2, libelle: 'accueil' },
|
|
|
|
|
|
{ siege: 2, libelle: 'animateur' },
|
|
|
|
|
|
]);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('une table inconnue est une faute du code ; des places ou des tours non entiers aussi', () => {
|
|
|
|
|
|
assert.throws(() => bilanChangementDefaut(garnie(), '6'), TypeError);
|
|
|
|
|
|
assert.throws(() => bilanTours(garnie(), 2.5), TypeError);
|
|
|
|
|
|
assert.throws(() => bilanSuppressionTable(garnie(), 9), TypeError);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|