Compare commits

...

4 Commits

Author SHA1 Message Date
gitea-actions
c255000a5e chore: bump version to v0.1.79
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 33s
2026-04-03 13:18:40 +00:00
b8b9368ad0 [#SIRH-6] Faire une doc de type wiki (#14)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #14
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-04-03 13:18:32 +00:00
gitea-actions
10a0ab0809 chore: bump version to v0.1.78
All checks were successful
Build & Push Docker Image / build (push) Successful in 38s
Auto Tag Develop / tag (push) Successful in 6s
2026-04-02 12:34:10 +00:00
055f1187f9 fix : wording
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
2026-04-02 14:34:00 +02:00
13 changed files with 780 additions and 4 deletions

View File

@@ -26,7 +26,8 @@
"Bash(pip3 install:*)",
"Bash(find:*)",
"Bash(git add:*)",
"Bash(git commit:*)"
"Bash(git commit:*)",
"Bash(python3:*)"
]
}
}

View File

@@ -2,6 +2,7 @@
## Mandatory Rules
- Any functional change MUST update `doc/` in the same intervention
- Any functional change MUST update the in-app documentation (`frontend/data/documentation-content.ts`) in the same intervention
- At the end of every feature addition or functional modification, update this CLAUDE.md to reflect new patterns, rules, or conventions introduced
## Commands
@@ -84,6 +85,16 @@
- Keep backend PHP DTOs aligned with frontend TS DTOs (`frontend/services/dto/*`)
- Update unit tests when constructor/service signatures change
## In-App Documentation
- Content: `frontend/data/documentation-content.ts` — structured TypeScript data with all user-facing documentation
- Types: `frontend/types/documentation.ts` — DocSection, DocArticle, DocBlock
- Composable: `frontend/composables/useDocumentation.ts` — role-based filtering (employee < site_manager < admin)
- Components: `frontend/components/documentation/` — DocumentationPage, DocumentationSection, DocumentationArticle
- Page: `frontend/pages/documentation.vue`
- 3 access levels: `employee` (ROLE_SELF), `site_manager` (ROLE_USER), `admin` (ROLE_ADMIN) — cumulative (admin sees everything)
- Each section/article has a `requiredLevel` that controls visibility
- When adding or modifying a feature, update the corresponding section in `documentation-content.ts`
## Language
- UI is in French
- User communicates in French

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '0.1.77'
app.version: '0.1.79'

View File

@@ -0,0 +1,26 @@
<template>
<article :id="`doc-${article.id}`" class="scroll-mt-6">
<h3 class="text-lg font-bold text-primary-500 mb-3">{{ article.title }}</h3>
<div class="space-y-3">
<template v-for="(block, idx) in article.blocks" :key="idx">
<p v-if="block.type === 'paragraph'" class="text-sm text-neutral-700 leading-relaxed">
{{ block.content }}
</p>
<ul v-else-if="block.type === 'list'" class="list-disc list-inside space-y-1 text-sm text-neutral-700 pl-2">
<li v-for="(item, i) in block.content.split('\n')" :key="i">{{ item }}</li>
</ul>
<div v-else-if="block.type === 'note'" class="bg-tertiary-500 border-l-4 border-primary-500 p-3 rounded-r-md">
<p class="text-sm text-neutral-700 leading-relaxed">{{ block.content }}</p>
</div>
</template>
</div>
</article>
</template>
<script setup lang="ts">
import type { DocArticle } from '~/types/documentation'
defineProps<{
article: DocArticle
}>()
</script>

View File

@@ -0,0 +1,67 @@
<template>
<div class="h-full flex gap-8">
<!-- Table des matières -->
<nav class="w-64 flex-shrink-0 overflow-y-auto pr-4 border-r border-neutral-200">
<h1 class="text-xl font-bold text-primary-500 mb-6">Documentation</h1>
<div v-for="section in visibleSections" :key="section.id" class="mb-4">
<div class="flex items-center gap-2 mb-1">
<Icon :name="section.icon" size="18" class="text-neutral-500"/>
<span class="text-sm font-semibold text-neutral-700">{{ section.title }}</span>
</div>
<ul class="pl-7 space-y-0.5">
<li v-for="article in section.articles" :key="article.id">
<button
class="text-xs text-neutral-500 hover:text-primary-500 text-left w-full py-0.5 transition-colors"
:class="activeArticleId === article.id ? 'text-primary-500 font-bold' : ''"
@click="scrollToArticle(article.id)"
>
{{ article.title }}
</button>
</li>
</ul>
</div>
</nav>
<!-- Contenu -->
<div ref="contentRef" class="flex-1 overflow-y-auto pr-4">
<DocumentationSection
v-for="section in visibleSections"
:key="section.id"
:section="section"
/>
</div>
</div>
</template>
<script setup lang="ts">
const { visibleSections, activeArticleId, scrollToArticle } = useDocumentation()
const contentRef = ref<HTMLElement | null>(null)
onMounted(() => {
if (!contentRef.value) return
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const id = entry.target.id.replace('doc-', '')
activeArticleId.value = id
break
}
}
},
{
root: contentRef.value,
rootMargin: '-10% 0px -80% 0px',
threshold: 0,
},
)
nextTick(() => {
const articles = contentRef.value?.querySelectorAll('[id^="doc-"]')
articles?.forEach(el => observer.observe(el))
})
onUnmounted(() => observer.disconnect())
})
</script>

View File

@@ -0,0 +1,23 @@
<template>
<section class="mb-10">
<div class="flex items-center gap-3 border-b-2 border-primary-500 pb-3 mb-6">
<Icon :name="section.icon" size="28" class="text-primary-500"/>
<h2 class="text-xl font-bold text-primary-500">{{ section.title }}</h2>
</div>
<div class="space-y-8 pl-2">
<DocumentationArticle
v-for="article in section.articles"
:key="article.id"
:article="article"
/>
</div>
</section>
</template>
<script setup lang="ts">
import type { DocSection } from '~/types/documentation'
defineProps<{
section: DocSection
}>()
</script>

View File

@@ -30,7 +30,7 @@
class="rounded-md bg-primary-500 px-8 py-2 font-bold text-white hover:bg-primary-600"
@click="openPaymentDrawer"
>
+ Payer les RRT
+ Payer les RTT
</button>
</div>
</div>

View File

@@ -0,0 +1,39 @@
import { documentationSections } from '~/data/documentation-content'
import type { DocAccessLevel, DocSection } from '~/types/documentation'
const LEVEL_HIERARCHY: Record<DocAccessLevel, number> = {
employee: 0,
site_manager: 1,
admin: 2,
}
function getUserLevel(roles: string[]): number {
if (roles.includes('ROLE_ADMIN') || roles.includes('ROLE_SUPER_ADMIN')) return 2
if (roles.includes('ROLE_USER')) return 1
return 0
}
export function useDocumentation() {
const auth = useAuthStore()
const userLevel = computed(() => getUserLevel(auth.user?.roles ?? []))
const visibleSections = computed<DocSection[]>(() => {
return documentationSections
.filter(s => LEVEL_HIERARCHY[s.requiredLevel] <= userLevel.value)
.map(s => ({
...s,
articles: s.articles.filter(a => LEVEL_HIERARCHY[a.requiredLevel] <= userLevel.value),
}))
.filter(s => s.articles.length > 0)
})
const activeArticleId = ref<string | null>(null)
const scrollToArticle = (articleId: string) => {
activeArticleId.value = articleId
const el = document.getElementById(`doc-${articleId}`)
el?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
return { visibleSections, activeArticleId, scrollToArticle }
}

View File

@@ -0,0 +1,568 @@
import type { DocSection } from '~/types/documentation'
export const documentationSections: DocSection[] = [
// ============================================================
// EMPLOYEE LEVEL
// ============================================================
{
id: 'connexion',
title: 'Connexion et navigation',
requiredLevel: 'employee',
icon: 'mdi:login',
articles: [
{
id: 'login',
title: 'Se connecter',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'Pour accéder à l\'application, rendez-vous sur la page de connexion et saisissez vos identifiants.' },
{ type: 'list', content: 'Saisissez votre nom d\'utilisateur\nSaisissez votre mot de passe\nCliquez sur le bouton "Connexion"' },
{ type: 'note', content: 'Si vous ne parvenez pas à vous connecter, contactez votre administrateur RH. Votre compte a peut-être été verrouillé.' },
],
},
{
id: 'navigation',
title: 'Naviguer dans la vue jour',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'La vue jour est votre écran principal. Elle affiche les heures de travail pour une date donnée.' },
{ type: 'list', content: 'Boutons "Hier" / "Aujourd\'hui" / "Demain" pour naviguer rapidement\nSélecteur de date pour choisir une date spécifique\nFiltrage par site si vous avez accès à plusieurs sites' },
{ type: 'paragraph', content: 'Seuls les employés ayant un contrat actif à la date sélectionnée sont affichés.' },
],
},
{
id: 'perimetre',
title: 'Périmètre d\'accès',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'Votre accès dépend du rôle qui vous a été attribué par l\'administrateur.' },
{ type: 'list', content: 'Employé : accès à la saisie de ses propres heures uniquement\nChef de site : accès aux heures des employés de ses sites autorisés + validation\nAdministrateur : accès complet à toutes les fonctionnalités' },
],
},
],
},
{
id: 'saisie-heures',
title: 'Saisie des heures',
requiredLevel: 'employee',
icon: 'mdi:clock-time-four-outline',
articles: [
{
id: 'saisie-time',
title: 'Mode horaire (TIME)',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'En mode horaire, vous saisissez vos heures via des créneaux matin, après-midi et soir.' },
{ type: 'list', content: 'Matin : heure de début et heure de fin\nAprès-midi : heure de début et heure de fin\nSoir : heure de début et heure de fin' },
{ type: 'paragraph', content: 'Le sélecteur de temps fonctionne par tranches de 15 minutes (00, 15, 30, 45). La saisie libre est possible mais sera corrigée automatiquement.' },
{ type: 'note', content: 'Les calculs sont mis à jour automatiquement : heures de jour (06:0021:00), heures de nuit (00:0006:00 et 21:0024:00) et total.' },
],
},
{
id: 'saisie-presence',
title: 'Mode présence (PRESENCE)',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'En mode présence (contrats forfait), vous indiquez simplement si vous étiez présent le matin et/ou l\'après-midi.' },
{ type: 'list', content: 'Cochez "Présent matin" pour indiquer une demi-journée de travail le matin\nCochez "Présent après-midi" pour indiquer une demi-journée l\'après-midi\nChaque demi-journée cochée compte pour 0.5 jour de présence' },
],
},
{
id: 'comprendre-calculs',
title: 'Comprendre les calculs affichés',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'Les colonnes de calcul sont mises à jour automatiquement en fonction de votre saisie.' },
{ type: 'list', content: 'Jour : total des heures dans la plage 06:0021:00\nNuit : total des heures dans les plages 00:0006:00 et 21:0024:00\nTotal : somme des heures de jour et de nuit' },
],
},
],
},
{
id: 'saisie-conducteurs',
title: 'Saisie conducteurs',
requiredLevel: 'employee',
icon: 'mdi:truck-outline',
articles: [
{
id: 'conducteur-heures',
title: 'Saisie des heures conducteur',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'Les conducteurs disposent d\'un écran dédié accessible via le menu "Heures Conducteurs". Ils n\'apparaissent pas sur l\'écran classique des heures.' },
{ type: 'list', content: 'Heures de jour : durée au format HH:MM\nHeures de nuit : durée au format HH:MM\nHeures atelier : durée au format HH:MM\nTotal : calculé automatiquement (jour + nuit + atelier)' },
],
},
{
id: 'conducteur-indemnites',
title: 'Indemnités conducteur',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'En plus des heures, vous pouvez cocher les indemnités correspondant à votre journée.' },
{ type: 'list', content: 'Petit déjeuner\nDéjeuner\nDîner\nNuitée' },
{ type: 'paragraph', content: 'La même logique de validation s\'applique que pour les heures classiques.' },
],
},
],
},
{
id: 'absences-validations',
title: 'Absences et validations',
requiredLevel: 'employee',
icon: 'mdi:information-outline',
articles: [
{
id: 'comprendre-absences',
title: 'Comprendre les absences affichées',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'Quand une absence est posée sur votre journée, elle apparaît dans la colonne dédiée avec un fond coloré selon le type d\'absence.' },
{ type: 'list', content: 'Absence du matin (AM) : verrouille le créneau matin\nAbsence de l\'après-midi (PM) : verrouille les créneaux après-midi et soir\nAbsence journée complète : verrouille tous les créneaux' },
{ type: 'note', content: 'Vous ne pouvez pas modifier les créneaux horaires verrouillés par une absence. Seul un administrateur peut retirer ou modifier l\'absence.' },
],
},
{
id: 'comprendre-validations',
title: 'Comprendre les validations',
requiredLevel: 'employee',
blocks: [
{ type: 'paragraph', content: 'Vos heures passent par un processus de double validation avant d\'être définitivement enregistrées.' },
{ type: 'list', content: 'Validation chef de site : votre chef de site vérifie et valide vos heures. La ligne est alors verrouillée pour vous.\nValidation RH : l\'administrateur RH valide définitivement. La ligne est complètement verrouillée.' },
{ type: 'paragraph', content: 'Une fois validée, vous ne pouvez plus modifier la ligne. Si une correction est nécessaire, contactez votre chef de site ou l\'administrateur RH.' },
{ type: 'note', content: 'Toute vraie modification effectuée par un administrateur remet automatiquement les deux validations à zéro.' },
],
},
],
},
// ============================================================
// SITE MANAGER LEVEL
// ============================================================
{
id: 'validation-site',
title: 'Validation de site',
requiredLevel: 'site_manager',
icon: 'mdi:check-decagram-outline',
articles: [
{
id: 'role-chef-site',
title: 'Rôle du chef de site',
requiredLevel: 'site_manager',
blocks: [
{ type: 'paragraph', content: 'En tant que chef de site, vous êtes responsable de la vérification et de la validation des heures saisies par les employés de votre site.' },
{ type: 'paragraph', content: 'Le workflow de validation suit un circuit en 3 étapes : l\'employé saisit ses heures → le chef de site valide → l\'admin RH valide définitivement.' },
],
},
{
id: 'validation-individuelle',
title: 'Validation individuelle',
requiredLevel: 'site_manager',
blocks: [
{ type: 'paragraph', content: 'Pour valider une ligne d\'heures individuellement :' },
{ type: 'list', content: 'Cochez la case de validation site sur la ligne de l\'employé\nLa ligne est immédiatement verrouillée pour l\'employé\nL\'administrateur RH peut toujours corriger une ligne que vous avez validée' },
],
},
{
id: 'validation-masse',
title: 'Validation en masse',
requiredLevel: 'site_manager',
blocks: [
{ type: 'paragraph', content: 'Pour gagner du temps, vous pouvez valider toutes les lignes en une seule action.' },
{ type: 'list', content: 'Cliquez sur le bouton de validation en masse\nToutes les lignes de la date affichée sont validées d\'un coup\nUtile quand toutes les saisies sont correctes' },
{ type: 'note', content: 'Quand toutes les lignes de votre site sont validées pour une date donnée, les administrateurs RH reçoivent automatiquement une notification.' },
],
},
{
id: 'difference-validations',
title: 'Validation site vs validation RH',
requiredLevel: 'site_manager',
blocks: [
{ type: 'paragraph', content: 'Il est important de comprendre la différence entre les deux niveaux de validation.' },
{ type: 'list', content: 'Validation site : verrouille la ligne pour les employés, mais l\'admin RH peut encore modifier\nValidation RH : verrouillage complet, seul l\'admin peut retirer cette validation\nLe chef de site ne voit pas et ne peut pas agir sur la validation RH' },
],
},
],
},
// ============================================================
// ADMIN LEVEL
// ============================================================
{
id: 'administration',
title: 'Administration',
requiredLevel: 'admin',
icon: 'mdi:cog-outline',
articles: [
{
id: 'gestion-sites',
title: 'Gestion des sites',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Les sites organisent les employés et les accès dans l\'application. Chaque site possède un nom et une couleur utilisée dans toute l\'interface.' },
{ type: 'list', content: 'Créer, modifier ou supprimer un site depuis le menu "Sites"\nL\'ordre d\'affichage est modifiable par glisser-déposer\nLa couleur du site est utilisée pour identifier visuellement les employés' },
],
},
{
id: 'gestion-types-absence',
title: 'Gestion des types d\'absence',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Les types d\'absence définissent les catégories disponibles lors de la pose d\'une absence.' },
{ type: 'list', content: 'Code : identifiant court (max 10 caractères), ex: C, M, AT\nLibellé : nom affiché, ex: Congé, Maladie, Accident du travail\nCouleur : code couleur pour le calendrier et la vue jour\nOption "Compté comme travaillé" : si activé, l\'absence crédite des heures en mode TIME' },
{ type: 'note', content: 'L\'option "Compté comme travaillé" impacte le calcul des heures supplémentaires. En mode TIME, les minutes sont créditées selon le contrat. En mode PRESENCE, aucun crédit n\'est appliqué.' },
],
},
{
id: 'gestion-utilisateurs',
title: 'Gestion des utilisateurs',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Chaque personne qui se connecte à l\'application a un compte utilisateur distinct de sa fiche employé.' },
{ type: 'list', content: 'Nom d\'utilisateur : unique, sert de login\nMot de passe : défini à la création, modifiable\nRôle : Admin (accès complet), User (chef de site), Self (employé)\nSites autorisés : pour les chefs de site, définit leur périmètre\nAssociation employé : lie le compte à une fiche employé\nVerrouillage : un compte verrouillé ne peut plus se connecter' },
{ type: 'note', content: 'Il n\'est pas possible de supprimer un utilisateur (sécurité). Pour bloquer l\'accès, utilisez le verrouillage de compte.' },
],
},
{
id: 'taches-automatiques',
title: 'Tâches automatiques (crons)',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Deux tâches automatiques s\'exécutent quotidiennement pour gérer le report des compteurs.' },
{ type: 'list', content: 'Report congés (02h10) : déclenche le report des congés payés le 1er juin (CDI/CDD) et le 1er janvier (forfait)\nReport RTT (02h15) : déclenche le report du solde RTT le 1er juin' },
{ type: 'note', content: 'Ces tâches sont idempotentes : si elles s\'exécutent plusieurs fois, aucun doublon n\'est créé.' },
],
},
],
},
{
id: 'employes-contrats',
title: 'Employés et contrats',
requiredLevel: 'admin',
icon: 'mdi:account-group-outline',
articles: [
{
id: 'liste-employes',
title: 'Liste et recherche d\'employés',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'La page Employés affiche tous les employés sous forme de cartes.' },
{ type: 'list', content: 'Recherche par nom\nFiltrage par site (multi-sélection)\nFiltrage par statut de contrat : "Avec contrat" (défaut), "Sans contrat", "Tous"\n"Avec contrat" = employés ayant une période de contrat active à la date du jour' },
],
},
{
id: 'creation-employe',
title: 'Créer un employé',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'La création d\'un employé se fait via le drawer d\'ajout.' },
{ type: 'list', content: 'Champs : prénom, nom, site\nNature du contrat : CDI, CDD ou INTERIM\nType de contrat / temps de travail (Forfait, 35h, 39h, etc.)\nDate de début (obligatoire)\nDate de fin (obligatoire pour CDD et INTERIM)' },
],
},
{
id: 'types-contrat',
title: 'Types de contrat',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Le type de contrat détermine le mode de suivi et les règles de calcul appliquées.' },
{ type: 'list', content: 'FORFAIT : suivi en jours (mode PRESENCE), base 218 jours/an\n35 HEURES : suivi horaire (mode TIME), 35h/semaine\n39 HEURES : suivi horaire (mode TIME), 39h/semaine\nCUSTOM : heures personnalisées (ex: 4h, 20h), 1h sup = 1h récup sans bonus\nINTERIM : travail temporaire, pas de récupération ni de congés gérés' },
{ type: 'note', content: 'Le mode de suivi (TIME ou PRESENCE) est lié au type de contrat et ne peut pas être modifié indépendamment.' },
],
},
{
id: 'suivi-contrat',
title: 'Suivi contrat et historique',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'L\'onglet "Suivi contrat" sur la fiche employé affiche l\'historique complet des périodes de contrat.' },
{ type: 'list', content: 'Chaque ligne : nature (CDI/CDD/INTERIM), type de contrat, date début, date fin ou "En cours"\nAjouter un contrat : disponible uniquement si le contrat en cours est clôturé\nClôturer un contrat : définir la date de fin + option "Solde de tout compte"\nSuspension : ajouter une période de suspension avec dates et commentaire' },
{ type: 'note', content: 'La case "Soldé dans le solde de tout compte" remet le report des congés à 0 pour l\'exercice suivant.' },
],
},
{
id: 'statut-conducteur',
title: 'Statut conducteur',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Le statut conducteur est un flag activé sur une période de contrat. Un employé peut changer de statut conducteur selon la période.' },
{ type: 'paragraph', content: 'Un employé conducteur apparaît uniquement sur l\'écran "Heures Conducteurs" et non sur l\'écran "Heures" classique.' },
],
},
],
},
{
id: 'double-validation',
title: 'Saisie et double validation',
requiredLevel: 'admin',
icon: 'mdi:shield-check-outline',
articles: [
{
id: 'validation-rh',
title: 'Validation RH',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'La validation RH est le niveau de validation le plus élevé, réservé aux administrateurs.' },
{ type: 'list', content: 'Verrouille complètement la ligne (heures et absences)\nSeul un administrateur peut retirer cette validation\nPeut être appliquée individuellement ou en masse' },
],
},
{
id: 'regles-reinitialisation',
title: 'Règles de réinitialisation',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Les validations sont automatiquement réinitialisées dans certaines conditions.' },
{ type: 'list', content: 'Toute vraie modification d\'une ligne remet les deux validations (site et RH) à faux\nUn enregistrement sans changement réel préserve les validations existantes\nLa date de modification est mise à jour uniquement quand un employé modifie ses propres heures' },
{ type: 'note', content: 'La date de modification est visible uniquement par les administrateurs, sous le nom de l\'employé dans la vue jour.' },
],
},
],
},
{
id: 'vue-semaine-hs',
title: 'Vue semaine et heures supplémentaires',
requiredLevel: 'admin',
icon: 'mdi:calendar-week',
articles: [
{
id: 'vue-semaine',
title: 'Vue semaine',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'La vue semaine est réservée aux administrateurs. Elle affiche une synthèse hebdomadaire par employé avec les heures supplémentaires calculées.' },
{ type: 'list', content: 'Filtrage par site et par employé\nDétail par jour avec totaux hebdomadaires\nColonnes de calcul : base, heures sup 25%, heures sup 50%, total récupération' },
],
},
{
id: 'calcul-hs',
title: 'Calcul des heures supplémentaires',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Les règles de calcul des heures supplémentaires dépendent du type de contrat.' },
{ type: 'list', content: 'Contrats ≤ 35h : +25% de 35h à 43h, +50% au-delà de 43h\nContrats ≥ 39h : +25% de 39h à 43h, +50% au-delà de 43h\nContrats CUSTOM (4h, 25h, etc.) : 1h supplémentaire = 1h de récupération, pas de bonus\nINTERIM : aucune récupération, aucun bonus' },
{ type: 'note', content: 'En cas de déficit hebdomadaire (heures travaillées < heures contrat), le déficit est déduit du cumul RTT : d\'abord des heures à 50%, puis des heures à 25%.' },
],
},
{
id: 'vue-semaine-conducteurs',
title: 'Vue semaine conducteurs',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'La vue semaine conducteurs affiche des colonnes spécifiques.' },
{ type: 'list', content: 'Totaux jour / nuit / atelier par jour et par semaine\nPanier de nuit (PN) : affiché quand heures nuit > heures jour OU nuit ≥ 4h\nCompteurs hebdomadaires : petit déjeuner, déjeuner, dîner, nuitée\nRTT calculé sur jour + nuit + atelier (au lieu des créneaux classiques)' },
],
},
],
},
{
id: 'absences-calendrier',
title: 'Absences et calendrier',
requiredLevel: 'admin',
icon: 'mdi:calendar-blank',
articles: [
{
id: 'poser-absence',
title: 'Poser une absence',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Les absences peuvent être posées depuis la vue jour des heures ou depuis le calendrier.' },
{ type: 'list', content: 'Journée complète : efface toutes les plages horaires\nDemi-journée matin (AM) : efface le créneau matin\nDemi-journée après-midi (PM) : efface les créneaux après-midi et soir' },
{ type: 'paragraph', content: 'Les absences sont stockées par jour : une absence de plusieurs jours est automatiquement découpée en entrées quotidiennes.' },
],
},
{
id: 'effet-absences-heures',
title: 'Effet sur les heures',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'L\'impact d\'une absence sur les heures dépend du type d\'absence et du mode de suivi.' },
{ type: 'list', content: 'Standard : efface les créneaux horaires correspondants\nSi "Compté comme travaillé" en mode TIME : crédite des minutes selon le contrat actif\nSi "Compté comme travaillé" en mode PRESENCE : aucun crédit (seules les cases cochées comptent)' },
{ type: 'note', content: 'Les absences comptées comme travaillées impactent le calcul des heures supplémentaires et du RTT.' },
],
},
{
id: 'calendrier-mensuel',
title: 'Calendrier mensuel',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Le calendrier offre une vue d\'ensemble mensuelle des absences de tous les employés.' },
{ type: 'list', content: 'Code couleur par type d\'absence\nDemi-journée : affichage en dégradé diagonal\nJournée complète : fond plein\nJours fériés : fond bleu clair\nFiltres par site et par employé\nNavigation par mois (précédent / suivant)' },
],
},
],
},
{
id: 'conges-payes',
title: 'Congés payés',
requiredLevel: 'admin',
icon: 'mdi:umbrella-beach-outline',
articles: [
{
id: 'regles-cdi-cdd',
title: 'Règles CDI/CDD non-forfait',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Pour les contrats CDI et CDD (hors forfait), l\'exercice de congés va du 1er juin (N-1) au 31 mai (N).' },
{ type: 'list', content: 'Acquisition annuelle : 25 jours + 5 samedis\nAcquisition mensuelle : 2,08 jours + 0,42 samedi par mois\nProratisation en cas de début/fin ou suspension en cours de mois\nContrat 4h : 10 jours annuels, 0 samedi, 0,83 jour/mois' },
],
},
{
id: 'regles-forfait',
title: 'Règles FORFAIT',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Pour les contrats forfait, l\'exercice suit l\'année civile (1er janvier au 31 décembre).' },
{ type: 'list', content: 'Calcul : jours ouvrés de l\'année 218 + bonus weekend/férié\nBonus : 1 jour par jour travaillé un weekend ou jour férié (0.5 si demi-journée)\nPas de samedis\nPas de jours en cours d\'acquisition' },
],
},
{
id: 'maladie-longue',
title: 'Arrêt maladie long',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'En cas d\'arrêt maladie de plus d\'un mois, les règles d\'acquisition sont modifiées.' },
{ type: 'list', content: 'Premier mois de maladie : acquisition normale\nAprès le premier mois : acquisition réduite (facteur 0,80)\nDétection automatique à partir des absences MALADIE consécutives (tolérance de gap ≤ 3 jours)' },
],
},
{
id: 'report-conges',
title: 'Report annuel et rollover',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Le reliquat de congés de l\'exercice précédent est automatiquement reporté dans les acquis du nouvel exercice.' },
{ type: 'list', content: 'Report automatique le 1er juin (CDI/CDD non-forfait) ou 1er janvier (forfait)\nSi "Solde de tout compte" coché sur le contrat clôturé : report remis à 0\nJours fractionnés : saisie manuelle par la RH, ajoutés aux acquis' },
],
},
{
id: 'consommation-conges',
title: 'Règle de consommation',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Les absences s\'imputent selon un ordre précis.' },
{ type: 'list', content: 'D\'abord sur les acquis (report N-1)\nPuis sur les jours en cours d\'acquisition\nEn cours d\'acquisition peut devenir négatif temporairement (se reconstitue avec les acquisitions suivantes)' },
{ type: 'paragraph', content: 'Compteurs visibles sur l\'onglet Congé de la fiche employé : acquis, en cours d\'acquisition, pris, restant.' },
],
},
],
},
{
id: 'rtt',
title: 'RTT (Récupération de Temps de Travail)',
requiredLevel: 'admin',
icon: 'mdi:timer-sand',
articles: [
{
id: 'rtt-principe',
title: 'Principe et exercice RTT',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Le RTT correspond aux heures supplémentaires accumulées, converties en temps de récupération. L\'exercice RTT va du 1er juin (N-1) au 31 mai (N).' },
{ type: 'paragraph', content: 'L\'onglet RTT sur la fiche employé affiche le détail hebdomadaire regroupé par mois, avec un compteur global en heures (1 jour = 7h = 420 minutes).' },
],
},
{
id: 'rtt-compteurs',
title: 'Compteurs RTT',
requiredLevel: 'admin',
blocks: [
{ type: 'list', content: 'Report N-1 : solde de l\'exercice précédent\nAcquis : cumul des heures supplémentaires de l\'exercice en cours\nDisponible : report + acquis payé\nPayé : RTT convertis en salaire (soustraits du disponible)' },
{ type: 'note', content: 'Les contrats INTERIM et le mode PRESENCE n\'accumulent pas de RTT (affiché à 0).' },
],
},
{
id: 'rtt-paiement',
title: 'Paiement RTT',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'L\'administrateur RH peut enregistrer un paiement RTT depuis l\'onglet RTT de la fiche employé.' },
{ type: 'list', content: 'Saisie : mois, nombre de minutes, taux (25% ou 50%)\nPlusieurs paiements possibles par mois\nLes heures payées sont soustraites du solde disponible' },
],
},
{
id: 'rtt-semaines-mois',
title: 'Attribution des semaines aux mois',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Chaque semaine ISO est attribuée à un seul mois dans le tableau RTT.' },
{ type: 'list', content: 'Une semaine est attribuée au mois qui contient son samedi\nSi le samedi tombe en début de mois suivant, la semaine est dans ce mois suivant' },
],
},
],
},
{
id: 'frais-primes-observations',
title: 'Frais, primes et observations',
requiredLevel: 'admin',
icon: 'mdi:account-cash-outline',
articles: [
{
id: 'frais',
title: 'Onglet Frais',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'L\'onglet Frais sur la fiche employé permet de saisir les frais kilométriques et les montants associés.' },
{ type: 'list', content: 'Mois : obligatoire\nKilomètres : nombre de km (optionnel)\nMontant : en euros (optionnel)\nCommentaire : optionnel\nDeux justificatifs PDF distincts : un pour les km, un pour le montant' },
{ type: 'note', content: 'Au moins un des deux champs (kilomètres ou montant) doit être supérieur à 0. Un seul enregistrement par mois par employé.' },
],
},
{
id: 'primes',
title: 'Onglet Prime',
requiredLevel: 'admin',
blocks: [
{ type: 'list', content: 'Mois : obligatoire\nMontant en euros : obligatoire\nCommentaire : optionnel\nUne seule prime par mois par employé' },
],
},
{
id: 'observations',
title: 'Onglet Observation',
requiredLevel: 'admin',
blocks: [
{ type: 'list', content: 'Mois : obligatoire\nTexte d\'observation : obligatoire\nUne seule observation par mois par employé\nNote libre pour le suivi RH' },
],
},
],
},
{
id: 'exports',
title: 'Exports et impressions',
requiredLevel: 'admin',
icon: 'mdi:file-pdf-box',
articles: [
{
id: 'export-recap-conges',
title: 'Export récap. congés',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Génère un PDF A4 portrait récapitulant les congés de tous les employés actifs.' },
{ type: 'list', content: 'Accessible depuis la page Employés (bouton "Export récap. congés")\nGénère un PDF à la date du jour\nDonnées groupées par site\nColonnes : nom, contrat, CP N-1 restant, samedi restant, CP N, RTT' },
],
},
{
id: 'export-recap-salaire',
title: 'Récapitulatif salaire',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Génère un PDF A4 paysage avec le détail mensuel pour la paie.' },
{ type: 'list', content: 'Sélecteur de mois (défaut = mois courant)\nDonnées groupées par site\nColonnes : nom, base contrat, jours de présence cadre, heures de nuit, panier de nuit, heures RTT payées, congés (nombre + dates), maladie/AT (nombre + dates), primes conducteur (PDJ, repas, nuitée, samedi), observations' },
],
},
{
id: 'impression-absences',
title: 'Impression absences',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Génère un PDF A3 paysage du calendrier d\'absences avec des filtres.' },
{ type: 'list', content: 'Filtres : période (du/au), sites, nature de contrat, type de contrat\nTous les filtres sont cochés par défaut\nCalendrier coloré par type d\'absence' },
],
},
{
id: 'export-heures-annuelles',
title: 'Export heures annuelles',
requiredLevel: 'admin',
blocks: [
{ type: 'paragraph', content: 'Génère un PDF par employé avec le détail jour par jour de ses heures sur une année.' },
{ type: 'list', content: 'Accessible depuis la fiche employé (bouton imprimante)\nChoix de l\'année civile (janvier à décembre)\nColonnes adaptées au mode de suivi (TIME, PRESENCE, conducteur)\nSections séparées en cas de changement de contrat en cours d\'année' },
],
},
],
},
]

View File

@@ -95,6 +95,16 @@
<Icon name="mdi:clipboard-text-clock-outline" size="24"/>
<p>Journal</p>
</NuxtLink>
<NuxtLink
to="/documentation"
class="flex items-center gap-2 py-3 text-md text-black hover:bg-tertiary-500 hover:text-primary-500"
:class="route.path.startsWith('/documentation')
? 'bg-tertiary-500 text-primary-500 font-bold'
: ''"
>
<Icon name="mdi:book-open-page-variant-outline" size="24"/>
<p>Documentation</p>
</NuxtLink>
</nav>
<div class="flex flex-col gap-2 items-center p-4">

View File

@@ -0,0 +1,9 @@
<template>
<DocumentationPage/>
</template>
<script setup lang="ts">
useHead({
title: 'Documentation',
})
</script>

View File

@@ -0,0 +1,21 @@
export type DocAccessLevel = 'employee' | 'site_manager' | 'admin'
export interface DocBlock {
type: 'paragraph' | 'list' | 'note'
content: string
}
export interface DocArticle {
id: string
title: string
requiredLevel: DocAccessLevel
blocks: DocBlock[]
}
export interface DocSection {
id: string
title: string
requiredLevel: DocAccessLevel
icon: string
articles: DocArticle[]
}

View File

@@ -8,6 +8,7 @@ use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Repository\AuditLogRepository;
use DateTimeImmutable;
use DateTimeZone;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RequestStack;
@@ -59,7 +60,7 @@ class AuditLogProvider implements ProviderInterface
'description' => $log->getDescription(),
'changes' => $log->getChanges(),
'affectedDate' => $log->getAffectedDate()?->format('Y-m-d'),
'createdAt' => $log->getCreatedAt()->format('Y-m-d H:i:s'),
'createdAt' => $log->getCreatedAt()->setTimezone(new DateTimeZone('Europe/Paris'))->format('Y-m-d H:i:s'),
];
}