diff --git a/.idea/SIRH.iml b/.idea/SIRH.iml
index 47ac152..b6dc1e9 100644
--- a/.idea/SIRH.iml
+++ b/.idea/SIRH.iml
@@ -154,6 +154,8 @@
+
+
diff --git a/.idea/php.xml b/.idea/php.xml
index c4734e1..6da05ea 100644
--- a/.idea/php.xml
+++ b/.idea/php.xml
@@ -155,6 +155,8 @@
+
+
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..7ae0446
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,75 @@
+# SIRH
+
+## Mandatory Rules
+- Any functional change MUST update `doc/` 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
+- `make start` — start Docker stack
+- `make test` — run backend tests (PHPUnit)
+- `make dev-nuxt` — dev frontend
+- `cd frontend && npm run build` — build frontend
+- `php bin/console cache:clear && php bin/console cache:warmup` — clear cache after deploy
+
+## Stack
+- Backend: Symfony + API Platform + Doctrine ORM
+- Frontend: Nuxt 4 + Vue 3 + TypeScript + Tailwind CSS
+
+## Project Structure
+- `src/` — Symfony domain, API resources, state providers/processors, services
+- `frontend/` — Nuxt app (pages, components, composables, services)
+- `migrations/` — Doctrine migrations (always include working `down()`)
+- `doc/` — functional rules and business documentation
+
+## Functional Rules
+- Reference: `doc/functional-rules.md` (mandatory reading before any business logic change)
+- Complementary: `doc/leave-rollover.md`, `doc/rtt-rollover.md`
+
+## Domain Model
+- Contracts: `trackingMode` (TIME=hours, PRESENCE=half-days), `weeklyHours`
+- Contract types: FORFAIT, THIRTY_FIVE_HOURS, THIRTY_NINE_HOURS, INTERIM, CUSTOM
+- Contract nature (per period): CDI, CDD, INTERIM
+- Employee contract history: `employee_contract_periods`, resolved by `EmployeeContractResolver`
+- Absences: stored per day (auto-split), AM/PM/full day, clear corresponding hour slots
+- Absences with `countAsWorkedHours=true`: credit minutes (TIME) or nothing (PRESENCE)
+
+## Validation Rules
+- `isValid` (RH): locks line for everyone (admin can only untoggle validation)
+- `isSiteValid` (site manager): locks for non-admin, admin can still edit
+- Any real modification resets both `isSiteValid=false` and `isValid=false`
+- No-op saves preserve existing validations
+
+## Overtime Rules
+- Contracts <= 35h: +25% from 35h to 43h, +50% beyond
+- Contracts >= 39h: +25% from 39h to 43h, +50% beyond
+- INTERIM: no overtime bonuses, no recovery time
+
+## Frontend Patterns
+
+### Table styling (standard across all pages)
+- Header: `grid border border-black bg-tertiary-500 px-6 py-3 text-[20px] font-semibold text-black rounded-t-md sticky top-0 z-10`
+- Body wrapper: `border-x border-b border-primary-500 rounded-b-md`
+- Rows: `grid items-center gap-4 border-b border-primary-500 px-6 py-3 text-md font-bold text-primary-500 last:border-b-0 cursor-pointer hover:bg-tertiary-500`
+- Page wrapper for scroll: `h-full flex flex-col overflow-hidden`, table container: `min-h-0 overflow-auto rounded-md bg-white`
+
+### Drawer buttons (AppDrawer)
+- Edit mode: `grid grid-cols-2 gap-3` → Supprimer (red, left) + Modifier (primary, right)
+- Create mode: centered `+ Ajouter` button, w-[200px]
+- Exception: Users drawer has NO delete button
+- All "Ajouter" buttons across the app use "+" prefix
+
+### API Platform (backend)
+- Custom operations use Processor (write) / Provider (read)
+- File uploads: `deserialize: false` on Post, access file via RequestStack
+- Upload dir: `%kernel.project_dir%/var/uploads`
+
+## Backend Conventions
+- Prefer explicit DTOs over associative arrays
+- Business rules in backend (providers/processors/services), frontend is display/interaction only
+- Keep backend PHP DTOs aligned with frontend TS DTOs (`frontend/services/dto/*`)
+- Update unit tests when constructor/service signatures change
+
+## Language
+- UI is in French
+- User communicates in French
+- Code (variables, comments) in English
diff --git a/composer.json b/composer.json
index c7ed2ef..f3e0a77 100644
--- a/composer.json
+++ b/composer.json
@@ -24,6 +24,7 @@
"symfony/flex": "^2",
"symfony/framework-bundle": "8.0.*",
"symfony/http-client": "8.0.*",
+ "symfony/mime": "8.0.*",
"symfony/monolog-bundle": "^4.0",
"symfony/property-access": "8.0.*",
"symfony/property-info": "8.0.*",
diff --git a/composer.lock b/composer.lock
index cb7c9e7..0238fc4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "b540b6cb25ef55c5eebccb57c76da584",
+ "content-hash": "bdc04f5145303388bac52809ea3f4b05",
"packages": [
{
"name": "api-platform/doctrine-common",
@@ -5374,6 +5374,92 @@
],
"time": "2026-01-28T10:46:31+00:00"
},
+ {
+ "name": "symfony/mime",
+ "version": "v8.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mime.git",
+ "reference": "5d26d1958aeeba2ace8cc64a3a93d4f5d8f8022b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/5d26d1958aeeba2ace8cc64a3a93d4f5d8f8022b",
+ "reference": "5d26d1958aeeba2ace8cc64a3a93d4f5d8f8022b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.4",
+ "symfony/polyfill-intl-idn": "^1.10",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "conflict": {
+ "egulias/email-validator": "~3.0.0",
+ "phpdocumentor/reflection-docblock": "<5.2|>=7",
+ "phpdocumentor/type-resolver": "<1.5.1"
+ },
+ "require-dev": {
+ "egulias/email-validator": "^2.1.10|^3.1|^4",
+ "league/html-to-markdown": "^5.0",
+ "phpdocumentor/reflection-docblock": "^5.2|^6.0",
+ "symfony/dependency-injection": "^7.4|^8.0",
+ "symfony/process": "^7.4|^8.0",
+ "symfony/property-access": "^7.4|^8.0",
+ "symfony/property-info": "^7.4|^8.0",
+ "symfony/serializer": "^7.4|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mime\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows manipulating MIME messages",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "mime",
+ "mime-type"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/mime/tree/v8.0.7"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-03-06T13:17:40+00:00"
+ },
{
"name": "symfony/monolog-bridge",
"version": "v8.0.4",
@@ -5685,6 +5771,93 @@
],
"time": "2025-06-27T09:58:17+00:00"
},
+ {
+ "name": "symfony/polyfill-intl-idn",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3",
+ "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2",
+ "symfony/polyfill-intl-normalizer": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Trevor Rowbotham",
+ "email": "trevor.rowbotham@pm.me"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "idn",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-10T14:38:51+00:00"
+ },
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.33.0",
diff --git a/frontend/components/AbsenceFormDrawer.vue b/frontend/components/AbsenceFormDrawer.vue
index 580cb1d..ec25735 100644
--- a/frontend/components/AbsenceFormDrawer.vue
+++ b/frontend/components/AbsenceFormDrawer.vue
@@ -93,28 +93,29 @@
/>
-
+
-
+
+
+
diff --git a/frontend/components/AbsencePrintDrawer.vue b/frontend/components/AbsencePrintDrawer.vue
index 0685439..e5e7683 100644
--- a/frontend/components/AbsencePrintDrawer.vue
+++ b/frontend/components/AbsencePrintDrawer.vue
@@ -96,17 +96,10 @@
-
-
+