Skip to content

fix(support): [HOTFIX] closing a ticket violated resolved-consistency constraint#377

Open
thierryvm wants to merge 2 commits into
mainfrom
fix/hotfix-ticket-close-constraint
Open

fix(support): [HOTFIX] closing a ticket violated resolved-consistency constraint#377
thierryvm wants to merge 2 commits into
mainfrom
fix/hotfix-ticket-close-constraint

Conversation

@thierryvm

@thierryvm thierryvm commented Jun 6, 2026

Copy link
Copy Markdown
Owner

[HOTFIX] Impossible de fermer un ticket support

Bug prod signalé par @Thierry : passer un ticket à « Fermé » depuis le triage super_admin (/app/admin) échoue avec « Mise à jour du statut impossible ». Aucun ticket ne pouvait être fermé.

Cause racine (vérifiée contre le schéma prod)

La contrainte support_tickets_resolved_consistency traite resolved ET closed comme états terminaux exigeant resolved_at + resolved_by non-null (et null pour open/in_progress). Or updateStatus ne posait resolved_* que pour resolved (isResolved) → un passage à closed envoyait resolved_at=nullviolation de contrainte → UPDATE rejeté (erreur générique, pas 42501).

Fix

updateStatus traite resolved et closed comme terminaux : préserve le stamp de résolution existant en fermant un ticket déjà résolu (ne pas écraser qui/quand), estampille now/self en entrant dans un état terminal depuis open/in_progress. tickets ajouté aux deps du callback pour lire la ligne courante.

Validation

  • Root cause prouvée : lecture de la contrainte en base + ticket réel 21f3df1e (resolved + resolved_* set → le fix le fermera).
  • 2 tests de régression (fermeture préserve le stamp ; fermeture directe estampille).
  • type-check ✅ · lint ✅ · 17/17 tests hook support ✅. Aucun changement DB/RLS (logique client uniquement).
  • code-reviewer + Voie A preview (reproduction de la fermeture en super_admin) avant merge.

🤖 Generated with Claude Code

Résumé par Sourcery

Garantir que les tickets de support considèrent à la fois les statuts « résolu » et « fermé » comme des états terminaux, avec des métadonnées de résolution cohérentes lors de la mise à jour du statut.

Corrections de bugs :

  • Corriger l’échec lors de la fermeture des tickets de support en conservant ou en définissant resolved_at et resolved_by pour les états terminaux afin de satisfaire la contrainte de cohérence de la base de données.

Tests :

  • Ajouter des tests de régression couvrant la fermeture d’un ticket déjà résolu et la fermeture directe depuis un ticket ouvert, afin de vérifier le comportement des métadonnées de résolution.
Original summary in English

Summary by Sourcery

Ensure support tickets treat both resolved and closed as terminal states with consistent resolution metadata when updating status.

Bug Fixes:

  • Fix failure when closing support tickets by preserving or stamping resolved_at and resolved_by for terminal states to satisfy the database consistency constraint.

Tests:

  • Add regression tests covering closing an already-resolved ticket and closing directly from an open ticket to verify resolution metadata behavior.

…traint [HOTFIX]

Changing a support ticket to "Fermé" (closed) from the super_admin triage UI
failed with "Mise à jour du statut impossible" — no ticket could ever be closed.

Root cause (verified against prod schema): the DB CHECK constraint
`support_tickets_resolved_consistency` treats BOTH 'resolved' AND 'closed' as
terminal states that require resolved_at + resolved_by to be non-null (and null
for 'open'/'in_progress'). But updateStatus only set resolved_* for 'resolved'
(`isResolved`), clearing them on every other transition — so a move to 'closed'
sent resolved_at=null, resolved_by=null and the UPDATE was rejected by the
constraint (a generic, non-42501 error → the generic FR message).

Fix: treat 'resolved' and 'closed' as terminal. Preserve the existing resolution
stamp when closing an already-resolved ticket (don't clobber when/who resolved
it); stamp now/self when entering a terminal state directly from open/in_progress.
Added `tickets` to the callback deps to read the current row.

- Reported by @Thierry on prod (real ticket 21f3df1e, resolved → could not close).
- 2 regression tests added (close preserves stamp; close-from-open stamps now/self).
- type-check + lint clean, 17/17 support hook tests pass. No DB/RLS change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
terminal-learning Ready Ready Preview, Comment Jun 6, 2026 10:17pm

@sourcery-ai

sourcery-ai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Guide du réviseur

Ajuste les mises à jour de statut des tickets de support afin que « resolved » et « closed » soient tous deux traités comme des états terminaux nécessitant des métadonnées de résolution non nulles, en préservant les tampons de résolution existants lors de la fermeture de tickets déjà résolus, en les appliquant lors de la fermeture depuis des états non terminaux, et en ajoutant des tests de régression ainsi que des mises à jour des dépendances des hooks pour couvrir ce comportement.

Diagramme de flux pour la gestion des états terminaux des tickets de support dans updateStatus

flowchart TD
  A[updateStatus called with id and next status] --> B[Compute isTerminal = next is resolved or closed]
  A --> C[Find current ticket by id in tickets]
  B --> D{isTerminal?}
  D -- No --> E[Set resolved_at = null
resolved_by = null]
  D -- Yes --> F{current has resolved_at and resolved_by?}
  F -- Yes --> G[Preserve current.resolved_at
and current.resolved_by]
  F -- No --> H[Set resolved_at = now
resolved_by = user.id]
  E --> I[Send PATCH with status and resolution fields]
  G --> I
  H --> I
Loading

Modifications au niveau des fichiers

Changement Détails Fichiers
Traiter « resolved » et « closed » comme des états terminaux lors de la mise à jour des tickets de support et s’assurer que les métadonnées de résolution sont gérées de manière cohérente pour satisfaire la contrainte de base de données.
  • Remplacer le drapeau isResolved par un drapeau isTerminal qui est vrai pour les statuts « resolved » et « closed ».
  • Rechercher le ticket actuel avant la mise à jour pour décider de préserver les valeurs existantes de resolved_at/resolved_by ou d’en appliquer de nouvelles.
  • Lors de la transition vers un état terminal depuis un état non terminal, définir resolved_at à maintenant et resolved_by à l’utilisateur courant ; sinon les vider pour les états non terminaux.
  • Mettre à jour la liste de dépendances du callback updateStatus pour inclure tickets afin que les données du ticket courant soient disponibles.
src/lib/hooks/useSupportTickets.ts
Ajouter des tests de régression pour vérifier que la fermeture des tickets respecte la sémantique de résolution des états terminaux et ne viole pas la contrainte de base de données.
  • Ajouter un test garantissant que la fermeture d’un ticket déjà résolu préserve ses valeurs existantes de resolved_at et resolved_by à la fois dans le patch et dans l’état local.
  • Ajouter un test garantissant que la fermeture d’un ticket directement depuis un état non terminal applique correctement resolved_at et resolved_by.
  • Utiliser des mocks de orderMock et updateMock pour simuler les interactions avec le backend et vérifier les champs générés dans le patch.
src/test/support/useSupportTickets.test.ts

Conseils et commandes

Interagir avec Sourcery

  • Déclencher une nouvelle revue : Commentez @sourcery-ai review sur la pull request.
  • Poursuivre les discussions : Répondez directement aux commentaires de revue de Sourcery.
  • Générer une issue GitHub à partir d’un commentaire de revue : Demandez à Sourcery de créer une issue à partir d’un commentaire de revue en y répondant. Vous pouvez aussi répondre à un commentaire de revue avec @sourcery-ai issue pour créer une issue à partir de celui-ci.
  • Générer un titre de pull request : Écrivez @sourcery-ai n’importe où dans le titre de la pull request pour générer un titre à tout moment. Vous pouvez aussi commenter @sourcery-ai title sur la pull request pour (re)générer le titre à tout moment.
  • Générer un résumé de pull request : Écrivez @sourcery-ai summary n’importe où dans le corps de la pull request pour générer un résumé de PR à tout moment, exactement là où vous le souhaitez. Vous pouvez aussi commenter @sourcery-ai summary sur la pull request pour (re)générer le résumé à tout moment.
  • Générer le guide du réviseur : Commentez @sourcery-ai guide sur la pull request pour (re)générer le guide du réviseur à tout moment.
  • Résoudre tous les commentaires Sourcery : Commentez @sourcery-ai resolve sur la pull request pour résoudre tous les commentaires Sourcery. Utile si vous avez déjà traité tous les commentaires et ne voulez plus les voir.
  • Ignorer toutes les revues Sourcery : Commentez @sourcery-ai dismiss sur la pull request pour ignorer toutes les revues Sourcery existantes. Particulièrement utile si vous voulez repartir de zéro avec une nouvelle revue – n’oubliez pas de commenter @sourcery-ai review pour déclencher une nouvelle revue !

Personnaliser votre expérience

Accédez à votre tableau de bord pour :

  • Activer ou désactiver des fonctionnalités de revue telles que le résumé de pull request généré par Sourcery, le guide du réviseur, et d’autres.
  • Changer la langue de revue.
  • Ajouter, supprimer ou modifier des instructions de revue personnalisées.
  • Ajuster d’autres paramètres de revue.

Obtenir de l’aide

Original review guide in English

Reviewer's Guide

Adjusts support ticket status updates so that both 'resolved' and 'closed' are treated as terminal states requiring non-null resolution metadata, preserving existing resolution stamps when closing already-resolved tickets, stamping them when closing from non-terminal states, and adds regression tests plus hook dependency updates to cover this behavior.

Flow diagram for support ticket terminal state handling in updateStatus

flowchart TD
  A[updateStatus called with id and next status] --> B[Compute isTerminal = next is resolved or closed]
  A --> C[Find current ticket by id in tickets]
  B --> D{isTerminal?}
  D -- No --> E[Set resolved_at = null
resolved_by = null]
  D -- Yes --> F{current has resolved_at and resolved_by?}
  F -- Yes --> G[Preserve current.resolved_at
and current.resolved_by]
  F -- No --> H[Set resolved_at = now
resolved_by = user.id]
  E --> I[Send PATCH with status and resolution fields]
  G --> I
  H --> I
Loading

File-Level Changes

Change Details Files
Treat both 'resolved' and 'closed' as terminal states when updating support tickets and ensure resolution metadata is handled consistently to satisfy the DB constraint.
  • Replace isResolved flag with an isTerminal flag that is true for both 'resolved' and 'closed' statuses.
  • Look up the current ticket before updating to decide whether to preserve existing resolved_at/resolved_by values or stamp new ones.
  • When transitioning to a terminal state from a non-terminal one, set resolved_at to now and resolved_by to the current user; otherwise clear them for non-terminal states.
  • Update the updateStatus callback dependency list to include tickets so current ticket data is available.
src/lib/hooks/useSupportTickets.ts
Add regression tests to verify closing tickets respects terminal-state resolution semantics and does not violate the DB constraint.
  • Add a test ensuring that closing an already-resolved ticket preserves its existing resolved_at and resolved_by values in both the patch and local state.
  • Add a test ensuring that closing a ticket directly from a non-terminal state stamps resolved_at and resolved_by appropriately.
  • Use mocks of orderMock and updateMock to simulate backend interactions and assert on the generated patch fields.
src/test/support/useSupportTickets.test.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Salut - j'ai laissé quelques retours de haut niveau :

  • Utiliser tickets dans le tableau de dépendances du callback updateStatus fera changer l'identité de la fonction à chaque modification de tickets. Si ce callback est largement propagé dans l'arborescence, envisage d'obtenir current à partir de la fonction de mise à jour (par exemple via setTickets ou un sélecteur dédié) afin de garder les dépendances minimales et d’éviter des re-rendus inutiles.
Invite pour les agents IA
Veuillez traiter les commentaires de cette revue de code :

## Commentaires généraux
- Utiliser `tickets` dans le tableau de dépendances du callback `updateStatus` fera changer l'identité de la fonction à chaque modification de `tickets`. Si ce callback est largement propagé dans l'arborescence, envisage d'obtenir `current` à partir de la fonction de mise à jour (par exemple via `setTickets` ou un sélecteur dédié) afin de garder les dépendances minimales et d’éviter des re-rendus inutiles.

Sourcery est gratuit pour l’open source - si nos revues vous plaisent, pensez à les partager ✨
Aidez-moi à être plus utile ! Cliquez sur 👍 ou 👎 sur chaque commentaire et j’utiliserai vos retours pour améliorer vos revues.
Original comment in English

Hey - I've left some high level feedback:

  • Using tickets in the updateStatus callback dependency array will cause the function identity to change on every tickets change; if this callback is passed down widely, consider deriving current from the updater (e.g. via setTickets or a dedicated selector) to keep dependencies minimal and avoid unnecessary re-renders.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using `tickets` in the `updateStatus` callback dependency array will cause the function identity to change on every tickets change; if this callback is passed down widely, consider deriving `current` from the updater (e.g. via `setTickets` or a dedicated selector) to keep dependencies minimal and avoid unnecessary re-renders.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

… (Sourcery #377)

Sourcery flagged that adding `tickets` to updateStatus's dep array rebuilds the
callback on every ticket change, re-rendering every TicketCard it is passed to.
Mirror `tickets` into a `ticketsRef` (synced by an effect, same pattern as the
existing isMountedRef/updatingIdRef) and read the current row from the ref, so
deps stay [user]. Behaviour unchanged: closing an already-resolved ticket still
preserves its resolution stamp. 17/17 tests pass, type-check + lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant