diff --git a/prisma/migrations/20260811130000_account_preferences/migration.sql b/prisma/migrations/20260811130000_account_preferences/migration.sql
new file mode 100644
index 0000000..7b96f74
--- /dev/null
+++ b/prisma/migrations/20260811130000_account_preferences/migration.sql
@@ -0,0 +1,67 @@
+-- ---------------------------------------------------------------------------
+-- Siège social et préférences du compte — PLAN.md §9.
+--
+-- Les colonnes d'adresse vont sur Account et non sur Location : le siège figure
+-- sur les documents édités et au registre du personnel, et il n'est pas
+-- nécessairement celui d'un magasin.
+--
+-- Les préférences sont une table à part plutôt que dix colonnes de plus sur
+-- Account : elles changent des calculs, se relisent ensemble, et la séparation
+-- garde lisible ce qui identifie l'entreprise et ce qui paramètre l'outil.
+-- ---------------------------------------------------------------------------
+
+ALTER TABLE "Account" ADD COLUMN "addressLine" TEXT;
+ALTER TABLE "Account" ADD COLUMN "postalCode" TEXT;
+ALTER TABLE "Account" ADD COLUMN "city" TEXT;
+ALTER TABLE "Account" ADD COLUMN "country" TEXT NOT NULL DEFAULT 'FR';
+ALTER TABLE "Account" ADD COLUMN "timezone" TEXT NOT NULL DEFAULT 'Europe/Paris';
+
+CREATE TABLE "AccountPreferences" (
+ "accountId" TEXT NOT NULL,
+
+ "defaultMealPerShift" BOOLEAN NOT NULL DEFAULT false,
+ "paidBreaks" BOOLEAN NOT NULL DEFAULT false,
+ "lockEmployeeMeals" BOOLEAN NOT NULL DEFAULT false,
+ "employeesSeeOwnTotals" BOOLEAN NOT NULL DEFAULT true,
+ "employeesSeeTeamPlanning" BOOLEAN NOT NULL DEFAULT false,
+ "hideForfaitCounters" BOOLEAN NOT NULL DEFAULT true,
+ "eveningShiftStartMinutes" INTEGER NOT NULL DEFAULT 1200,
+
+ "employeesEditOwnProfile" BOOLEAN NOT NULL DEFAULT true,
+ "employeesSeeOwnTimesheets" BOOLEAN NOT NULL DEFAULT true,
+ "employeesSeeTeamContacts" BOOLEAN NOT NULL DEFAULT false,
+ "managerCreatedArePlannable" BOOLEAN NOT NULL DEFAULT false,
+ "directorsSharePeople" BOOLEAN NOT NULL DEFAULT false,
+ "directorsAccessHrDashboard" BOOLEAN NOT NULL DEFAULT true,
+ "directorsSeePaidLeave" BOOLEAN NOT NULL DEFAULT true,
+ "managersSeePaidLeave" BOOLEAN NOT NULL DEFAULT false,
+
+ "smoothOvertimeMonthly" BOOLEAN NOT NULL DEFAULT false,
+ "includeRestInNormalHours" BOOLEAN NOT NULL DEFAULT false,
+ "autoEmployeeNumber" BOOLEAN NOT NULL DEFAULT false,
+
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "AccountPreferences_pkey" PRIMARY KEY ("accountId")
+);
+
+ALTER TABLE "AccountPreferences" ADD CONSTRAINT "AccountPreferences_accountId_fkey"
+ FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- ---------------------------------------------------------------------------
+-- Isolation par compte.
+--
+-- La clé primaire *est* l'identifiant de compte, si bien qu'une fuite
+-- supposerait déjà de connaître l'identifiant visé. La politique reste posée :
+-- la défense en profondeur ne se dispense pas au motif que l'attaque serait
+-- malcommode.
+-- ---------------------------------------------------------------------------
+
+ALTER TABLE "AccountPreferences" ENABLE ROW LEVEL SECURITY;
+ALTER TABLE "AccountPreferences" FORCE ROW LEVEL SECURITY;
+
+CREATE POLICY tenant_isolation ON "AccountPreferences"
+ USING ("accountId" = planflow_current_account());
+
+CREATE POLICY tenant_insert ON "AccountPreferences"
+ FOR INSERT WITH CHECK ("accountId" = planflow_current_account());
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index bc4076b..81f61f8 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -19,6 +19,14 @@ model Account {
name String
siren String?
apeCode String?
+ /// Siège social. Il figure sur les documents édités et au registre unique du
+ /// personnel ; il n'est pas déduit de l'établissement, qui peut être ailleurs.
+ addressLine String?
+ postalCode String?
+ city String?
+ country String @default("FR")
+ /// Fuseau du compte. Un établissement peut le surcharger (Location.timezone).
+ timezone String @default("Europe/Paris")
collectiveAgreementId String?
/// Surcharges d'accord d'entreprise (PLAN.md §6.3). Vide aujourd'hui :
/// l'organisation auditée n'a pas d'accord d'entreprise.
@@ -37,6 +45,69 @@ model Account {
documentTemplates DocumentTemplate[]
legalConfig LegalConfigEntry[]
installation Installation?
+ preferences AccountPreferences?
+}
+
+/// Préférences du compte — PLAN.md §9, réglages « préférences » et « paie ».
+///
+/// Trois blocs typés plutôt qu'un sac de clés-valeurs. Ce n'est pas de la
+/// rigueur pour la rigueur : `smoothOvertimeMonthly` change le **calcul** des
+/// heures supplémentaires, et un réglage qui change un calcul doit être lisible
+/// dans le schéma, migrable, et impossible à renseigner de travers. Un JSON
+/// libre rendrait indétectable une clé mal orthographiée — la préférence
+/// paraîtrait active et ne le serait pas.
+///
+/// Une ligne par compte, créée à la demande : l'absence de ligne vaut « tous
+/// les défauts », et les défauts sont ceux du modèle.
+model AccountPreferences {
+ accountId String @id
+
+ // --- Plannings ---------------------------------------------------------
+ /// Compter un repas par créneau sans saisie.
+ defaultMealPerShift Boolean @default(false)
+ /// Pauses payées : elles cessent alors d'être déduites du temps de travail.
+ paidBreaks Boolean @default(false)
+ /// Interdire au salarié de modifier son repas.
+ lockEmployeeMeals Boolean @default(false)
+ /// Le salarié voit son total d'heures planifiées et réelles de la semaine.
+ employeesSeeOwnTotals Boolean @default(true)
+ /// Le salarié voit le planning des autres membres de son équipe.
+ employeesSeeTeamPlanning Boolean @default(false)
+ /// Masquer les compteurs — et les alertes — des salariés au forfait jours.
+ /// Un forfait jours ne se compte pas en heures : afficher un écart
+ /// hebdomadaire sur ces lignes produit une alerte qui n'a pas de sens.
+ hideForfaitCounters Boolean @default(true)
+ /// Heure à partir de laquelle un créneau est « du soir », en minutes depuis
+ /// minuit. 1200 = 20:00.
+ eveningShiftStartMinutes Int @default(1200)
+
+ // --- Droits ------------------------------------------------------------
+ employeesEditOwnProfile Boolean @default(true)
+ employeesSeeOwnTimesheets Boolean @default(true)
+ /// Annuaire ouvert : téléphone et adresse e-mail de toute l'équipe.
+ employeesSeeTeamContacts Boolean @default(false)
+ /// Un salarié créé par un manager est planifiable sans validation.
+ managerCreatedArePlannable Boolean @default(false)
+ directorsSharePeople Boolean @default(false)
+ directorsAccessHrDashboard Boolean @default(true)
+ directorsSeePaidLeave Boolean @default(true)
+ managersSeePaidLeave Boolean @default(false)
+
+ // --- Paie --------------------------------------------------------------
+ /// Décompter les heures supplémentaires au mois plutôt qu'à la semaine.
+ ///
+ /// **Risque juridique.** Le décompte hebdomadaire est le principe (art.
+ /// L3121-28) ; lisser au mois suppose un accord d'aménagement du temps de
+ /// travail. Faux par défaut, et l'écran le dit.
+ smoothOvertimeMonthly Boolean @default(false)
+ /// Inclure repos et absences dans les heures normales.
+ includeRestInNormalHours Boolean @default(false)
+ /// Générer le matricule à la création plutôt que de le saisir.
+ autoEmployeeNumber Boolean @default(false)
+
+ updatedAt DateTime @updatedAt
+
+ account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
}
/// Marqueur d'installation de l'instance — une ligne, jamais deux.
diff --git a/src/app/(app)/reglages/compte/page.tsx b/src/app/(app)/reglages/compte/page.tsx
new file mode 100644
index 0000000..f878c2a
--- /dev/null
+++ b/src/app/(app)/reglages/compte/page.tsx
@@ -0,0 +1,44 @@
+import Link from 'next/link';
+
+import { AccountIdentityForm } from '@/components/settings/AccountIdentityForm';
+import { PageBody, PageHeader } from '@/components/shell/PageHeader';
+import { Card, CardHeader, EmptyState } from '@/components/ui/Card';
+import { getAccountIdentity } from '@/server/settings/account';
+
+export const metadata = { title: 'Informations du compte · PlanFlow' };
+export const dynamic = 'force-dynamic';
+
+export default async function ComptePage() {
+ const account = await getAccountIdentity();
+
+ return (
+
+
+
+ {account === null ? (
+
+
+
+ ) : (
+
+
+
+
+ )}
+
+
+ Le siège n’est pas un établissement : il porte l’identité juridique, là
+ où{' '}
+
+ les établissements
+ {' '}
+ portent le SIRET, le fuseau effectif et les plannings. Une entreprise
+ dont le siège n’ouvre aucun magasin garde donc une adresse ici et aucune
+ équipe.
+
+
+ );
+}
diff --git a/src/app/(app)/reglages/paie/page.tsx b/src/app/(app)/reglages/paie/page.tsx
new file mode 100644
index 0000000..d13138a
--- /dev/null
+++ b/src/app/(app)/reglages/paie/page.tsx
@@ -0,0 +1,38 @@
+import Link from 'next/link';
+
+import { PreferenceBlock } from '@/components/settings/PreferenceForms';
+import { PageBody, PageHeader } from '@/components/shell/PageHeader';
+import { Card, CardHeader } from '@/components/ui/Card';
+import { getPreferences } from '@/server/settings/account';
+
+export const metadata = { title: 'Réglages de paie · PlanFlow' };
+export const dynamic = 'force-dynamic';
+
+export default async function ReglagesPaiePage() {
+ const preferences = await getPreferences();
+
+ return (
+
+
+
+
+
+
+
+
+
+ Ces trois réglages changent des chiffres, pas un affichage. Une période
+ déjà verrouillée n’est pas recalculée : le changement vaut pour les
+ périodes ouvertes, et les{' '}
+
+ instantanés de paie
+ {' '}
+ antérieurs restent tels qu’ils ont été figés. C’est ce qui garde une paie
+ de mars reproductible après une modification en juin.
+
+ Ces réglages valent pour tout le compte. Ils s’ajoutent aux capacités des
+ rôles sans les remplacer : ouvrir un droit ici ne donne rien à qui n’a
+ pas la capacité correspondante, et le fermer la retire à tout le monde.
+