---
suivi: 1628
date: 2026-09-22
sujet: Page saisie TVA sur marge (PR foncier + notaire)
chantier: programmes / financier
type: feature
statut: poussé
hash: b8f97ce6
fichiers:
  - app/Http/Controllers/ProgrammeTvaMargeController.php
  - app/Http/Requests/UpdateProgrammeTvaMargeRequest.php
  - app/Models/ProgrammeTvaMarge.php
  - app/Services/ProgrammeTvaMargeService.php
  - app/Support/ProgrammeTvaMargeAcces.php
  - database/migrations/2026_09_22_100000_create_programme_tva_marges_table.php
  - resources/js/Components/Programmes/FinancierSubNav.vue
  - resources/js/Layouts/AppShell.vue
  - resources/js/Pages/TvaMarge/Index.vue
  - routes/web.php
  - tests/Unit/TvaMarge1628SourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1628_page_tva_marge.md
---

# SUIVI #1628 — page TVA SUR MARGE (saisie PR + notaire)

## PROMPT ENVOYÉ

Livrer uniquement la page de saisie du PR foncier sous FINANCIER
(#tva-marge) : PR + frais de notaire, total, surface de référence hors
ODF. Aucun calcul TVA, aucun sous-lot, grille inchangée. Table
extensible aux tranches. Contrôle serveur m365GroupNames brut.
Arbitrages Robin acquis (parent calculé, on vend le parent, etc.) :
ne pas les recoder ici.

## FICHES LUES

- **#1622 LEÇON** — deux pages Inertia + même barre Teleport ; hash
  seul ne porte pas le Budget (ni la TVA). 403 via m365GroupNames brut.
  Impact : 3ᵉ sous-onglet = nouvelle route `/tva-marge`, pas un hash
  sur le Bilan. Compte Robin (GS_ADMIN_ERP) ne prouve pas le 403.
- **#1626** — pas de `pr_foncier` / `frais_notaire` sur `programmes` ;
  table dédiée `tranche_id` NULL ; surface = `surface_terrain` hors
  `mode_vente=odf` ; collision Vite FinancierSubNav.
- **#1351** — Teleport `#…-subnav-host` + ObjectSubNavBar.
- **#1188 / zones communes** — AppShell : un build, rebase origin/main
  avant Vite.

## AVANT MODIFICATION (constat)

FinancierSubNav : Teleport `#financier-subnav-host` (hôte AppShell si
Bilan/Index ou Budget/Index). Items BILAN + BUDGET (si GS_ADMIN_ERP
brut). Hash `#bilan` / `#budget` ; changement d’onglet = `router.visit`
vers `/bilan` ou `/budget`. 3ᵉ onglet : item + HASH + `urlFor` +
étendre l’hôte à `TvaMarge/Index`.

BUDGET 403 : `assertBudgetOngletAdminErp()` —
`in_array('GS_ADMIN_ERP', $user->m365GroupNames(), true)`. Middleware
`m365.groups` short-circuit `isAdminERP()` — ne pas s’en servir pour
TVA SUR MARGE.

Colonnes `programmes` : `Schema::getColumnListing` impossible en local
(MySQL 127.0.0.1 refusé, même constat #1626). Dump `hectareenvol.sql`
CREATE TABLE + fillable modèle : pas de `pr_foncier` ni `frais_notaire`.
Dump en retard sur `commercialisation_active_*` (#1582) — toujours pas
ces deux montants.

## SYNTHÈSE

Table `programme_tva_marges` (forme #1626) : `programme_id`,
`tranche_id` nullable (NULL = programme), `pr_foncier`, `frais_notaire`
DECIMAL(15,2) HT. Pas de FK tranche (table absente). Pas de colonne
générée STORED. Unicité NULL MySQL non fiable : upsert
`whereNull('tranche_id')` + `lockForUpdate`.

Montants **HT** (base de calcul TVA, comptes 331000+331200), label HT
à l’écran.

Droits proposés (arbitrage) : GS_COMPTABILITE, GS_DIRECTION_GENERALE,
GS_ADMIN_ERP. Compte de test 403 : commercial GS_HECTARE ou GS_ENVOL
sans ces trois groupes (pas le compte Robin).

## DÉPLOIEMENT

```
git pull origin main
php artisan migrate --force
php artisan optimize:clear
php artisan view:clear
php artisan route:clear
php artisan journal:sync
```

Pas de npm sur OVH.

## LEÇON

Un 3ᵉ sous-onglet FINANCIER n’est pas un hash de plus sur le Bilan :
nouvelle page + étendre `showFinancierSubnavHost` / `isFinancierNavActive`
/ exclusion du bandeau titre. Oublier l’hôte Teleport = barre invisible.
`m365.groups` middleware ne convient pas (isAdminERP). Unique MySQL
`(programme_id, tranche_id)` n’empêche pas deux lignes NULL.
