Deux des cinq vues de §9 manquaient. La vue des présences répond à une question que les quatre autres posent mal : qui est là aujourd'hui. La grille montre des créneaux, pas des personnes ; il fallait la lire colonne par colonne et compter de tête. Le planning à afficher est une mise en page pour la feuille punaisée en salle de pause — toutes les équipes à la suite, sans panneau d'édition ni compteurs, un aplat par créneau avec l'horaire et le code du poste écrits. Sans la capacité de voir le non publié, la feuille ne la contourne pas : imprimer un brouillon revient à le publier sur un mur. La règle « absent ou présent » part dans le domaine plutôt que dans les écrans. Les deux vues en tirent la même conclusion, et un export futur en tirera la même : deux implémentations finiraient par se contredire sur le seul cas qui compte, celui du congé validé après la construction de la semaine. Les créneaux restent alors au planning, et l'absence l'emporte — afficher « présent » enverrait un responsable chercher en rayon quelqu'un qui est chez lui. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import type { BoardAbsence, BoardRow } from '@/domain/planning/board';
|
|
|
|
/**
|
|
* Lecture « présent / absent / repos » d'une ligne de planning.
|
|
*
|
|
* Vit dans le domaine et non dans l'écran : la vue des présences, la feuille
|
|
* imprimée et, demain, un export en tirent la même conclusion. Deux
|
|
* implémentations de cette règle finiraient par se contredire sur le seul cas
|
|
* qui compte — celui du salarié dont le congé a été validé après coup.
|
|
*/
|
|
|
|
export type DayState = 'present' | 'absent' | 'off';
|
|
|
|
/**
|
|
* Absence recouvrant la colonne demandée.
|
|
*
|
|
* Les absences d'une ligne sont déjà découpées sur la semaine affichée :
|
|
* `startDay` est un index de colonne, `span` un nombre de jours. Repartir des
|
|
* dates ici referait, moins bien, un travail déjà fait — et rouvrirait la
|
|
* question du fuseau au passage.
|
|
*/
|
|
export function absenceOnDay(
|
|
row: BoardRow,
|
|
dayIndex: number,
|
|
): BoardAbsence | undefined {
|
|
return row.absences.find(
|
|
(absence) =>
|
|
dayIndex >= absence.startDay && dayIndex < absence.startDay + absence.span,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* État d'un salarié un jour donné.
|
|
*
|
|
* **L'absence l'emporte sur le créneau.** Un congé accepté après la
|
|
* construction du planning laisse les créneaux en base : afficher « présent »
|
|
* enverrait un responsable chercher en rayon quelqu'un qui est chez lui. Le
|
|
* planning a tort, pas l'absence.
|
|
*/
|
|
export function dayState(row: BoardRow, dayIndex: number): DayState {
|
|
if (absenceOnDay(row, dayIndex)) return 'absent';
|
|
return (row.days[dayIndex]?.length ?? 0) > 0 ? 'present' : 'off';
|
|
}
|
|
|
|
export interface DayHeadcount {
|
|
present: number;
|
|
absent: number;
|
|
off: number;
|
|
}
|
|
|
|
/**
|
|
* Effectif par jour sur un ensemble de lignes.
|
|
*
|
|
* Les lignes de besoins non couverts sont écartées : elles ne portent personne,
|
|
* et les compter en « repos » gonflerait un effectif qui n'existe pas.
|
|
*/
|
|
export function headcountByDay(
|
|
rows: BoardRow[],
|
|
dayCount: number,
|
|
): DayHeadcount[] {
|
|
const counted = rows.filter((row) => !row.unassigned);
|
|
|
|
return Array.from({ length: dayCount }, (_, index) => {
|
|
const tally: DayHeadcount = { present: 0, absent: 0, off: 0 };
|
|
for (const row of counted) {
|
|
tally[dayState(row, index)] += 1;
|
|
}
|
|
return tally;
|
|
});
|
|
}
|