feat(gating): réserver le fractionnement de transactions à l’édition Base #369

Closed
opened 2026-10-03 13:34:22 +00:00 by maximus · 0 comments
Owner

Contexte

Décision de Max du 2026-10-03, prise pendant l'analyse de la-compagnie-maximus#180 (tableau comparatif des éditions sur la page produit) : le fractionnement de transactions (« Répartition » dans l'interface) fait partie de l'édition Base, avec les ajustements.

Aujourd'hui l'app ne le verrouille pas sur la page Transactions, alors que le texte d'incitation à l'achat le promet déjà dans Base : upsell.features.adjustments, « Ajoutez des écritures manuelles et fractionnez des transactions sur plusieurs catégories » (fr.json:1240, en.json:1240). L'issue aligne donc le code sur une promesse existante ; elle ne change pas l'offre.

Analyse (/analyze, 2026-10-03, main 0e4fef5)

Prémisse confirmée, avec ces précisions :

  • Deux points d'entrée. TransactionsPage.tsx:102 → TransactionTable.tsx:72,216,306-314 → useTransactions.ts:318,336,351, non verrouillé ; et AdjustmentsPage.tsx:9-17,57-80, qui ouvre la même SplitAdjustmentModal mais est déjà verrouillée par sa route (App.tsx:120). Aucun autre écran ne crée de fractionnement.
  • Les données vivent dans transactions (parent_transaction_id, is_split, ligne enfant de compensation, transactionService.ts:386-458). Les rapports, le tableau de bord et le budget ne filtrent jamais sur ces colonnes : les fractionnements existants restent comptés quelle que soit l'édition. C'est conforme à l'ADR 0017 (L88 : « le gating bloque l'accès, jamais les données »).
  • L'export/import chiffré (dataExportService.ts) conserve is_split et parent_transaction_id : à laisser tel quel (Gratuite).
  • Docs : la matrice de l'ADR 0017 (L34-45) ne nomme pas le fractionnement et range Transactions en Gratuite ; docs/guide-utilisateur.md:13,150,160 et fr.json/en.json:784,901 décrivent la Répartition sans mention d'édition.

Décision de Max (2026-10-03) : incitation pour toute ligne, icône gardée. En Gratuite, le bouton Répartition porte un cadenas et ouvre l'incitation à l'achat (« Obtenir Base ») au lieu de la fenêtre, que la ligne soit déjà fractionnée ou non. L'icône orange continue de signaler une ligne fractionnée. Les fractionnements existants restent en base et comptés. Écartés : une lecture seule de la fenêtre, et l'annulation permise en Gratuite.

Travail à faire

  • Prédicat pur (par exemple splitGate / isSplitLocked, sur le modèle de src/shared/profileGate.ts) : verrouillé seulement si ready && !allowed pour adjustments, via useEntitlement (src/hooks/useEntitlement.ts:13). Pendant le chargement de l'édition, ni verrou ni fenêtre (comme RequireFeature).
  • TransactionTable.tsx : en Gratuite, le bouton affiche un cadenas (motif « verrouillé, jamais masqué » de ProfileFormModal.tsx:173-197) et ouvre une fenêtre qui enveloppe <UpsellGate feature="adjustments" requiredTier={requiredTierFor("adjustments")} onNavigate={close} /> (modèle ProfileSwitcher.tsx:148-165). La couleur orange de row.is_split est conservée.
  • ADR 0017 : la matrice nomme la Répartition dans la ligne Ajustements (Base) ; préciser que les fractionnements existants restent comptés en Gratuite.
  • Guide (docs/guide-utilisateur.md, sections Transactions et éditions) et docs intégrées (docs.*, FR et EN) : la Répartition est une fonction Base.
  • CHANGELOG.md et CHANGELOG.fr.md (Changed/Modifié) : la Répartition quitte l'édition gratuite, les répartitions existantes restent comptées.
  • Une fois la release publiée, prévenir la-compagnie-maximus#180, dont le tableau attend cette release.

Surface de test

  • Existants : src/shared/profileGate.test.ts, src/shared/upsellCta.test.ts, src/services/entitlements.test.ts, src/components/shared/UpsellCtas.render.test.tsx (renderToStaticMarkup, environnement node, sans jsdom) ; src/__integration__/transactions-transfer-icon.test.ts lit la source de TransactionTable.tsx. Aucun test de SplitAdjustmentModal.
  • À ajouter :
    • Prédicat : verrouillé en Gratuite prête, ouvert en Base et Premium, jamais verrouillé avant ready.
    • Rendu statique : en Gratuite, le bouton porte le cadenas ; en Base, il n'en porte pas ; une ligne fractionnée garde la classe orange dans les deux cas.
    • Si le composant le permet sans jsdom : le clic en Gratuite n'ouvre pas SplitAdjustmentModal. Sinon, une vérification manuelle et une ligne dans la PR.

Critères d'acceptation

  • En Gratuite, Répartition ouvre l'incitation à l'achat, pas la fenêtre de fractionnement, pour toute ligne.
  • En Base et Premium, comportement inchangé.
  • Aucune donnée existante perdue ; les fractionnements existants restent comptés dans les rapports et signalés par l'icône.
  • ADR, guide, docs intégrées et CHANGELOG à jour.
  • npm test et la CI verts.

Complexité estimée

Simple.

Liens

la-compagnie-maximus#180 (bloquée par cette issue et sa release) ; ADR 0017 ; #346 (UpsellGate, onNavigate).

## Contexte Décision de Max du 2026-10-03, prise pendant l'analyse de la-compagnie-maximus#180 (tableau comparatif des éditions sur la page produit) : le fractionnement de transactions (« Répartition » dans l'interface) fait partie de l'édition Base, avec les ajustements. Aujourd'hui l'app ne le verrouille pas sur la page Transactions, alors que le texte d'incitation à l'achat le promet déjà dans Base : `upsell.features.adjustments`, « Ajoutez des écritures manuelles et fractionnez des transactions sur plusieurs catégories » (`fr.json:1240`, `en.json:1240`). L'issue aligne donc le code sur une promesse existante ; elle ne change pas l'offre. ## Analyse (`/analyze`, 2026-10-03, `main` `0e4fef5`) Prémisse confirmée, avec ces précisions : - **Deux points d'entrée.** `TransactionsPage.tsx:102` → `TransactionTable.tsx:72,216,306-314` → `useTransactions.ts:318,336,351`, non verrouillé ; et `AdjustmentsPage.tsx:9-17,57-80`, qui ouvre la même `SplitAdjustmentModal` mais est déjà verrouillée par sa route (`App.tsx:120`). Aucun autre écran ne crée de fractionnement. - **Les données vivent dans `transactions`** (`parent_transaction_id`, `is_split`, ligne enfant de compensation, `transactionService.ts:386-458`). Les rapports, le tableau de bord et le budget ne filtrent jamais sur ces colonnes : les fractionnements existants restent comptés quelle que soit l'édition. C'est conforme à l'ADR 0017 (L88 : « le gating bloque l'accès, jamais les données »). - **L'export/import chiffré** (`dataExportService.ts`) conserve `is_split` et `parent_transaction_id` : à laisser tel quel (Gratuite). - **Docs** : la matrice de l'ADR 0017 (L34-45) ne nomme pas le fractionnement et range Transactions en Gratuite ; `docs/guide-utilisateur.md:13,150,160` et `fr.json`/`en.json:784,901` décrivent la Répartition sans mention d'édition. **Décision de Max (2026-10-03) : incitation pour toute ligne, icône gardée.** En Gratuite, le bouton Répartition porte un cadenas et ouvre l'incitation à l'achat (« Obtenir Base ») au lieu de la fenêtre, que la ligne soit déjà fractionnée ou non. L'icône orange continue de signaler une ligne fractionnée. Les fractionnements existants restent en base et comptés. Écartés : une lecture seule de la fenêtre, et l'annulation permise en Gratuite. ## Travail à faire - [ ] Prédicat pur (par exemple `splitGate` / `isSplitLocked`, sur le modèle de `src/shared/profileGate.ts`) : verrouillé seulement si `ready && !allowed` pour `adjustments`, via `useEntitlement` (`src/hooks/useEntitlement.ts:13`). Pendant le chargement de l'édition, ni verrou ni fenêtre (comme `RequireFeature`). - [ ] `TransactionTable.tsx` : en Gratuite, le bouton affiche un cadenas (motif « verrouillé, jamais masqué » de `ProfileFormModal.tsx:173-197`) et ouvre une fenêtre qui enveloppe `<UpsellGate feature="adjustments" requiredTier={requiredTierFor("adjustments")} onNavigate={close} />` (modèle `ProfileSwitcher.tsx:148-165`). La couleur orange de `row.is_split` est conservée. - [ ] ADR 0017 : la matrice nomme la Répartition dans la ligne Ajustements (Base) ; préciser que les fractionnements existants restent comptés en Gratuite. - [ ] Guide (`docs/guide-utilisateur.md`, sections Transactions et éditions) et docs intégrées (`docs.*`, FR et EN) : la Répartition est une fonction Base. - [ ] `CHANGELOG.md` et `CHANGELOG.fr.md` (Changed/Modifié) : la Répartition quitte l'édition gratuite, les répartitions existantes restent comptées. - [ ] Une fois la release publiée, prévenir la-compagnie-maximus#180, dont le tableau attend cette release. ## Surface de test - Existants : `src/shared/profileGate.test.ts`, `src/shared/upsellCta.test.ts`, `src/services/entitlements.test.ts`, `src/components/shared/UpsellCtas.render.test.tsx` (`renderToStaticMarkup`, environnement node, sans jsdom) ; `src/__integration__/transactions-transfer-icon.test.ts` lit la source de `TransactionTable.tsx`. Aucun test de `SplitAdjustmentModal`. - À ajouter : - [ ] Prédicat : verrouillé en Gratuite prête, ouvert en Base et Premium, jamais verrouillé avant `ready`. - [ ] Rendu statique : en Gratuite, le bouton porte le cadenas ; en Base, il n'en porte pas ; une ligne fractionnée garde la classe orange dans les deux cas. - [ ] Si le composant le permet sans jsdom : le clic en Gratuite n'ouvre pas `SplitAdjustmentModal`. Sinon, une vérification manuelle et une ligne dans la PR. ## Critères d'acceptation - [ ] En Gratuite, Répartition ouvre l'incitation à l'achat, pas la fenêtre de fractionnement, pour toute ligne. - [ ] En Base et Premium, comportement inchangé. - [ ] Aucune donnée existante perdue ; les fractionnements existants restent comptés dans les rapports et signalés par l'icône. - [ ] ADR, guide, docs intégrées et CHANGELOG à jour. - [ ] `npm test` et la CI verts. ## Complexité estimée Simple. ## Liens la-compagnie-maximus#180 (bloquée par cette issue et sa release) ; ADR 0017 ; #346 (`UpsellGate`, `onNavigate`).
maximus added the
status:triage
type:feature
source:human
labels 2026-10-03 13:34:23 +00:00
maximus added
status:ready
and removed
status:triage
labels 2026-10-03 17:54:01 +00:00
maximus added
status:in-progress
and removed
status:ready
labels 2026-10-17 00:04:21 +00:00
maximus added
status:review
and removed
status:in-progress
labels 2026-10-17 00:04:50 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-10-17 00:26:49 +00:00
maximus added
status:review
and removed
status:approved
labels 2026-10-17 11:37:44 +00:00
maximus removed the
status:review
label 2026-10-17 11:40:27 +00:00
maximus referenced this issue from a commit 2026-10-17 11:46:17 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#369
No description provided.