diff --git a/README.de.md b/README.de.md index 3ba40d84..31636889 100644 --- a/README.de.md +++ b/README.de.md @@ -1,6 +1,8 @@ -# Scrumooth +# Scrumooth – Blockiere das Rauschen. Liefere reibungslos. -**Selbst gehostetes Scrum-Tool, getreu dem Scrum Guide** +_Der Linter für Scrum._ + +_Selbst gehostet, Open Source und entwickelt, um den Scrum Guide 2020 durchzusetzen._ > **Sprachen:** [English](README.md) | [Deutsch](README.de.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) @@ -14,15 +16,42 @@ [](https://nodejs.org/) [](https://www.postgresql.org/) -Scrumooth ist ein selbst gehostetes Scrum-Tool, das den Scrum Guide getreu umsetzt. Dank seines bewusst schlanken Designs führt es Teams durch den gesamten Scrum-Lebenszyklus — vom Product Goal und Backlog bis hin zu Sprint Review und Sprint Retrospective — ohne die Komplexität schwergewichtiger SaaS-Plattformen. Stellen Sie es auf Ihrer eigenen Infrastruktur bereit, behalten Sie die Kontrolle über Ihre Daten und zahlen Sie niemals pro Benutzer. -
+ + 👉 Live-Demo auf GitHub Pages starten + +
+ +> **Hinweis:** Die Demo verwendet In-Memory-Mock-Daten — alle von Ihnen vorgenommenen Änderungen sind nur lokal in Ihrer Browser-Sitzung gültig und werden beim Aktualisieren zurückgesetzt. Für persistente Daten und Mehrbenutzer-Zusammenarbeit folgen Sie der Anleitung unter [Installation](#installation), um Ihre eigene Instanz selbst zu hosten. + +--- + + + +## 📜 Das Manifest — Warum Scrumooth existiert + +> Die meisten Projektmanagement-Tools sind **passive Tonbandgeräte**. +> Sie liefern Boards, sie protokollieren Ihre Klicks, sie zeichnen schöne Diagramme—_nachdem_ der Sprint gescheitert ist. +> Sie verfolgen Ihre Fehler. Sie hindern Sie nie daran, sie zu begehen. +> +> **Scrumooth dreht den Spieß um.** Wir sind der **Gatekeeper**, nicht der Notenschreiber. +> +> Wir gießen den **Scrum Guide 2020** in ausführbaren Code. Wir schlagen Best Practices nicht nur vor—wir **erzwingen** sie nativ, sodass Ihr Team weniger Zeit damit verbringt, über Prozesse zu diskutieren, und mehr Zeit damit, funktionierende Software auszuliefern. + ## Inhaltsverzeichnis - [Live-Demo](#live-demo) +- [Das Manifest](#the-manifesto) - [Funktionen](#features) - [Tech-Stack](#tech-stack) - [Schnellstart](#quick-start) diff --git a/README.es.md b/README.es.md index d372118d..64335ac6 100644 --- a/README.es.md +++ b/README.es.md @@ -1,6 +1,8 @@ -# Scrumooth +# Scrumooth – Silencia el ruido. Entrega sin fricciones. -**Herramienta de Scrum autohospedada, fiel a la Scrum Guide** +_El linter de Scrum._ + +_Autohospedado, de código abierto y creado para hacer cumplir la Scrum Guide 2020._ > **Idiomas:** [English](README.md) | [Deutsch](README.de.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) @@ -14,15 +16,42 @@ [](https://nodejs.org/) [](https://www.postgresql.org/) -Scrumooth es una herramienta de Scrum autohospedada que implementa fielmente la Scrum Guide. Ligera por diseño, guía a los equipos a lo largo de todo el ciclo de vida de Scrum — desde el Product Goal y el backlog hasta la Sprint Review y la retrospectiva — sin la complejidad de las plataformas SaaS pesadas. Despliéguela en su propia infraestructura, mantenga sus datos bajo su control y no pague nunca por usuario. -
+ + 👉 Abrir la demo en vivo en GitHub Pages + +
+ +> **Nota:** La demo utiliza datos simulados en memoria — cualquier cambio que realice es local a su sesión del navegador y se restablece al recargar la página. Para datos persistentes y colaboración multiusuario, siga la guía de [Instalación](#installation) para autohospedar su propia instancia. + +--- + + + +## 📜 El manifiesto — Por qué existe Scrumooth + +> La mayoría de las herramientas de gestión de proyectos son **grabadoras de cinta pasivas**. +> Te dan tableros, registran tus clics, dibujan gráficos bonitos—_después_ de que el Sprint fracasa. +> Rastrean tus errores. Nunca te impiden cometerlos. +> +> **Scrumooth invierte el guion.** Somos el **guardián**, no el apuntador. +> +> Incorporamos la **Scrum Guide 2020** como código ejecutable. No solo sugerimos buenas prácticas—las **hacemos cumplir** de forma nativa, para que tu equipo dedique menos tiempo a discutir sobre procesos y más tiempo a entregar software que funciona. + ## Tabla de contenidos - [Demo en vivo](#live-demo) +- [El manifiesto](#the-manifesto) - [Características](#features) - [Stack tecnológico](#tech-stack) - [Inicio rápido](#quick-start) diff --git a/README.fr.md b/README.fr.md index ed0abae6..6ad19e08 100644 --- a/README.fr.md +++ b/README.fr.md @@ -1,6 +1,8 @@ -# Scrumooth +# Scrumooth – Filtrez le bruit. Livrez en toute fluidité. -**Outil Scrum auto-hébergé, fidèle au Scrum Guide** +_Le linter de Scrum._ + +_Auto-hébergé, open source et conçu pour faire respecter le Scrum Guide 2020._ > **Langues :** [English](README.md) | [Deutsch](README.de.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) @@ -14,15 +16,42 @@ [](https://nodejs.org/) [](https://www.postgresql.org/) -Scrumooth est un outil Scrum auto-hébergé qui implémente fidèlement le Scrum Guide. Conçu pour rester léger, il accompagne les équipes tout au long du cycle de vie Scrum — du Product Goal et du backlog jusqu'à la Sprint Review et à la rétrospective — sans la complexité des plateformes SaaS lourdes. Déployez-le sur votre propre infrastructure, gardez le contrôle de vos données et ne payez jamais par utilisateur. -
+ + 👉 Lancer la démo en ligne sur GitHub Pages + +
+ +> **Remarque :** La démo utilise des données simulées en mémoire — toute modification que vous apportez reste locale à votre session de navigateur et est réinitialisée lors du rafraîchissement. Pour des données persistantes et une collaboration multi-utilisateurs, suivez le guide d'[Installation](#installation) afin d'auto-héberger votre propre instance. + +--- + + + +## 📜 Le manifeste — Pourquoi Scrumooth existe + +> La plupart des outils de gestion de projet sont des **magnétophones passifs**. +> Ils vous fournissent des tableaux, ils enregistrent vos clics, ils dessinent de beaux graphiques—_après_ que le Sprint a échoué. +> Ils suivent vos erreurs. Ils ne vous empêchent jamais de les commettre. +> +> **Scrumooth renverse la situation.** Nous sommes le **gardien**, pas le secrétaire. +> +> Nous intégrons le **Scrum Guide 2020** comme du code exécutable. Nous ne nous contentons pas de suggérer les bonnes pratiques—nous les **faisons respecter** nativement, afin que votre équipe passe moins de temps à débattre des processus et plus de temps à livrer des logiciels fonctionnels. + ## Table des matières - [Démo en ligne](#live-demo) +- [Le manifeste](#the-manifesto) - [Fonctionnalités](#features) - [Pile technologique](#tech-stack) - [Démarrage rapide](#quick-start) diff --git a/README.it.md b/README.it.md index 129cb0f1..09887ab9 100644 --- a/README.it.md +++ b/README.it.md @@ -1,6 +1,8 @@ -# Scrumooth +# Scrumooth – Blocca il rumore. Consegna senza intoppi. -**Strumento Scrum self-hosted, fedele alla Scrum Guide** +_Il linter di Scrum._ + +_Self-hosted, open source e progettato per far rispettare la Scrum Guide 2020._ > **Lingue:** [English](README.md) | [Deutsch](README.de.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) @@ -14,15 +16,42 @@ [](https://nodejs.org/) [](https://www.postgresql.org/) -Scrumooth è uno strumento Scrum self-hosted che implementa fedelmente la Scrum Guide. Leggero per impostazione, guida i team lungo l'intero ciclo di vita di Scrum — dal Product Goal e dal backlog fino alla Sprint Review e alla Sprint Retrospective — senza la complessità delle piattaforme SaaS più pesanti. Distribuiscilo sulla tua infrastruttura, mantieni i tuoi dati sotto il tuo controllo e non pagare mai per utente. -
+ + 👉 Avvia la demo dal vivo su GitHub Pages + +
+ +> **Nota:** La demo utilizza dati simulati in memoria — qualsiasi modifica apporti è locale alla sessione del browser e viene azzerata al refresh. Per dati persistenti e collaborazione multi-utente, segui la guida all'[Installazione](#installation) per ospitare autonomamente la tua istanza. + +--- + + + +## 📜 Il manifesto — Perché Scrumooth esiste + +> La maggior parte degli strumenti di gestione dei progetti sono **registratori passivi**. +> Ti danno board, registrano i tuoi clic, disegnano grafici bellissimi—_dopo_ che lo Sprint è fallito. +> Tracciano i tuoi errori. Non ti impediscono mai di commetterli. +> +> **Scrumooth ribalta il copione.** Siamo il **guardiano**, non l'annotatore. +> +> Trasformiamo la **Scrum Guide 2020** in codice eseguibile. Non ci limitiamo a suggerire le best practice—le **facciamo rispettare** in modo nativo, così il tuo team passa meno tempo a discutere sui processi e più tempo a consegnare software funzionante. + ## Indice - [Demo dal vivo](#live-demo) +- [Il manifesto](#the-manifesto) - [Funzionalità](#features) - [Stack tecnologico](#tech-stack) - [Avvio rapido](#quick-start) diff --git a/README.md b/README.md index 40c3d0d6..2c8593e7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# Scrumooth +# Scrumooth – Block the noise. Ship the smooth. -**Self-hosted Scrum tool, faithful to the Scrum Guide** +_The Linter for Scrum._ + +_Self-hosted, open-source, and built to enforce the 2020 Scrum Guide._ > **Languages:** [English](README.md) | [Deutsch](README.de.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) @@ -14,15 +16,42 @@ [](https://nodejs.org/) [](https://www.postgresql.org/) -Scrumooth is a self-hosted Scrum tool that faithfully implements the Scrum Guide. Lightweight by design, it guides teams through the full Scrum lifecycle — from product goal and backlog to sprint review and retrospective — without the complexity of heavy SaaS platforms. Deploy it on your own infrastructure, keep your data under your control, and never pay per user. -
+ + 👉 Launch the Live Demo on GitHub Pages + +
+ +> **Note:** The demo uses in‑memory mock data — any changes you make are local to your browser session and reset on refresh. For persistent data and multi‑user collaboration, follow the [Installation](#installation) guide to self‑host your own instance. + +--- + + + +## 📜 The Manifesto — Why Scrumooth exists + +> Most project management tools are **passive tape recorders**. +> They give you boards, they log your clicks, they draw beautiful charts—_after_ the Sprint fails. +> They track your mistakes. They never stop you from making them. +> +> **Scrumooth flips the script.** We are the **gatekeeper**, not the note-taker. +> +> We embed the **2020 Scrum Guide** as executable code. We don't just suggest best practices—we **enforce** them natively, so your team spends less time arguing about process and more time shipping working software. + ## Table of Contents - [Live Demo](#live-demo) +- [The Manifesto](#the-manifesto) - [Features](#features) - [Tech Stack](#tech-stack) - [Quick Start](#quick-start) @@ -39,20 +68,6 @@ Scrumooth is a self-hosted Scrum tool that faithfully implements the Scrum Guide - [Contributing](#contributing) - [License](#license) - - -## 🚀 Live Demo - -Try Scrumooth instantly in your browser — no installation required. The demo runs with mock data (no backend needed) so you can explore the full Scrum lifecycle right away. - -- - 👉 Launch the Live Demo on GitHub Pages - -
- -> **Note:** The demo uses in‑memory mock data — any changes you make are local to your browser session and reset on refresh. For persistent data and multi‑user collaboration, follow the [Installation](#installation) guide to self‑host your own instance. - ## ✨ Features diff --git a/docs/api/daily-scrum.md b/docs/api/daily-scrum.md index d4df93e9..89b22d64 100644 --- a/docs/api/daily-scrum.md +++ b/docs/api/daily-scrum.md @@ -1,63 +1,70 @@ # Daily Scrum API -Complete Daily Scrum API reference for daily update management, team status tracking, and impediment promotion. +Complete Daily Scrum API reference for the team-level, goal-focused Daily Scrum (`/api/v1/daily-scrums`). ## Table of Contents - [Overview](#overview) - [Authentication](#authentication) -- [Endpoints](#endpoints) - - [Get Team Members with Update Status](#get-team-members-with-update-status) - - [Get Daily Updates for Sprint](#get-daily-updates-for-sprint) - - [Create Daily Update](#create-daily-update) - - [Get Daily Update by ID](#get-daily-update-by-id) - - [Update Daily Update](#update-daily-update) - - [Delete Daily Update](#delete-daily-update) - - [Promote Impediment](#promote-impediment) - - [Send Daily Update Reminder](#send-daily-update-reminder) +- [Team-Level Daily Scrum Endpoints](#team-level-daily-scrum-endpoints) + - [Get Today's Daily Scrum](#get-todays-daily-scrum) + - [Get Daily Scrums for Sprint](#get-daily-scrums-for-sprint) + - [Create Daily Scrum](#create-daily-scrum) + - [Get Daily Scrum by ID](#get-daily-scrum-by-id) + - [Update Daily Scrum](#update-daily-scrum) + - [Record Participation](#record-participation) + - [Get Participation](#get-participation) + - [Send Team-Wide Signal](#send-team-wide-signal) + - [Promote Impediment from Daily Scrum](#promote-impediment-from-daily-scrum) - [Error Codes](#error-codes) - [Best Practices](#best-practices) ## Overview -The Daily Scrum API provides comprehensive daily update management capabilities including: +The Daily Scrum is a 15-minute event for the Developers to inspect progress toward the Sprint Goal and adapt the Sprint Backlog. Scrumooth models this as a **team-level record** per Sprint per day, not a per-user status report. -- Daily update creation and editing -- Team member update status tracking -- Impediment promotion from daily updates to formal records -- Daily update reminder notifications -- Sprint-scoped update retrieval with date filtering +### Team-Level Daily Scrum (`/api/v1/daily-scrums`) -All endpoints are scoped under `/api/v1/daily-updates`. +The team-level API provides: + +- A single team-level Daily Scrum record per Sprint per date +- Goal-focused fields (`progressNotes`, `adaptationsNotes`, `planForNextDay`) +- Developer-chosen structure (`focusMode`) saved with the record +- Optional Sprint Backlog adjustment linkage +- Participation tracking (who contributed, not who "owes" a report) +- A neutral team-wide signal to gather the Developers +- Impediment promotion from the Daily Scrum to a formal record ## Authentication All daily scrum endpoints require authentication. Include the access token in your request: +Reading endpoints are open to any team member. Because the Daily Scrum is an event for the Developers (Scrum Guide), the write endpoints that create, update, or record participation for a Daily Scrum require the caller to hold the **Developer** role in the sprint's team. A non-Developer (e.g. Product Owner or Scrum Master) receives `403 Forbidden` when attempting these operations. + **Using Cookies (Recommended)** ```http -GET /api/v1/daily-updates/:sprintId +GET /api/v1/daily-scrums/:sprintId/today Cookie: accessToken=eyJhbGc... ``` **Using Bearer Token** ```http -GET /api/v1/daily-updates/:sprintId +GET /api/v1/daily-scrums/:sprintId/today Authorization: Bearer eyJhbGc... ``` -## Endpoints +## Team-Level Daily Scrum Endpoints -### Get Team Members with Update Status +### Get Today's Daily Scrum -Get team members along with their daily update submission status for a specific sprint and date. +Get the team-level Daily Scrum for a sprint, defaulting to today. **Endpoint** ``` -GET /api/v1/daily-updates/:sprintId/team-status +GET /api/v1/daily-scrums/:sprintId/today ``` **Authentication** @@ -70,7 +77,7 @@ GET /api/v1/daily-updates/:sprintId/team-status **Query Parameters** -- `date` (string, optional): Date in YYYY-MM-DD format. If omitted, returns empty submitted and pending lists. +- `date` (string, optional): Date in YYYY-MM-DD format. Defaults to today. **Success Response** @@ -81,19 +88,17 @@ Content-Type: application/json { "success": true, "data": { - "submitted": [ + "id": "550e8400-e29b-41d4-a716-446655440010", + "sprintId": "550e8400-e29b-41d4-a716-446655440000", + "scrumDate": "2026-05-10", + "progressNotes": "On track toward the Sprint Goal", + "adaptationsNotes": "Reassigned password-reset to Carol", + "planForNextDay": "Carol and Bob pair on password-reset email", + "focusMode": "goal", + "participants": [ { - "id": "550e8400-e29b-41d4-a716-446655440010", - "sprintId": "550e8400-e29b-41d4-a716-446655440000", + "id": "550e8400-e29b-41d4-a716-446655440030", "userId": "550e8400-e29b-41d4-a716-446655440001", - "updateDate": "2026-05-10", - "yesterdayWork": "Completed login page", - "todayWork": "Working on dashboard", - "impediment": null, - "createdAt": "2026-05-10T09:00:00.000Z", - "createdBy": "550e8400-e29b-41d4-a716-446655440001", - "updatedAt": "2026-05-10T09:00:00.000Z", - "updatedBy": "550e8400-e29b-41d4-a716-446655440001", "user": { "id": "550e8400-e29b-41d4-a716-446655440001", "firstName": "John", @@ -102,59 +107,34 @@ Content-Type: application/json } } ], - "pending": [ + "backlogAdjustments": [ { - "userId": "550e8400-e29b-41d4-a716-446655440002", - "userName": "Jane Smith" + "id": "550e8400-e29b-41d4-a716-446655440040", + "sprintBacklogItemId": "550e8400-e29b-41d4-a716-446655440050", + "action": "reassigned", + "sprintBacklogItem": { + "id": "550e8400-e29b-41d4-a716-446655440050", + "pbiId": "550e8400-e29b-41d4-a716-446655440060", + "pbi": { "id": "550e8400-e29b-41d4-a716-446655440060", "title": "Password reset" } + } } ] } } ``` -**Error Responses** - -**400 Bad Request - Invalid Date Format** - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "Invalid date format. Expected YYYY-MM-DD" - } -} -``` - -**404 Not Found - Sprint Not Found** - -```json -{ - "success": false, - "error": { - "code": "NOT_FOUND", - "message": "Sprint not found" - } -} -``` - -**Example Request** - -```bash -curl -X GET "https://api.scrumooth.dev/api/v1/daily-updates/550e8400-e29b-41d4-a716-446655440000/team-status?date=2026-05-10" \ - -b cookies.txt -``` +When no record exists for the date, `data` is `null`. --- -### Get Daily Updates for Sprint +### Get Daily Scrums for Sprint -Get all daily updates for a specific sprint, optionally filtered by date. +Get all team-level Daily Scrums for a sprint, optionally filtered by date. **Endpoint** ``` -GET /api/v1/daily-updates/:sprintId +GET /api/v1/daily-scrums/:sprintId ``` **Authentication** @@ -167,7 +147,7 @@ GET /api/v1/daily-updates/:sprintId **Query Parameters** -- `date` (string, optional): Date in YYYY-MM-DD format to filter updates +- `date` (string, optional): Date in YYYY-MM-DD format **Success Response** @@ -181,58 +161,28 @@ Content-Type: application/json { "id": "550e8400-e29b-41d4-a716-446655440010", "sprintId": "550e8400-e29b-41d4-a716-446655440000", - "userId": "550e8400-e29b-41d4-a716-446655440001", - "updateDate": "2026-05-10", - "yesterdayWork": "Completed login page", - "todayWork": "Working on dashboard", - "impediment": "Blocked by API dependency", - "createdAt": "2026-05-10T09:00:00.000Z", - "createdBy": "550e8400-e29b-41d4-a716-446655440001", - "updatedAt": "2026-05-10T09:00:00.000Z", - "updatedBy": "550e8400-e29b-41d4-a716-446655440001", - "user": { - "id": "550e8400-e29b-41d4-a716-446655440001", - "firstName": "John", - "lastName": "Doe", - "email": "john@example.com" - }, - "impedimentRecord": null + "scrumDate": "2026-05-10", + "progressNotes": "On track toward the Sprint Goal", + "adaptationsNotes": null, + "planForNextDay": "Pair on feature Y", + "focusMode": "impediment", + "participants": [], + "backlogAdjustments": [] } ] } ``` -**Error Responses** - -**400 Bad Request - Invalid Date Format** - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "Invalid date format. Expected YYYY-MM-DD" - } -} -``` - -**Example Request** - -```bash -curl -X GET "https://api.scrumooth.dev/api/v1/daily-updates/550e8400-e29b-41d4-a716-446655440000?date=2026-05-10" \ - -b cookies.txt -``` - --- -### Create Daily Update +### Create Daily Scrum -Create a new daily update for the authenticated user in a sprint. Only one update per user per day is allowed. +Create the team-level Daily Scrum for a sprint on today's date. Only one record per Sprint per day is allowed. **Endpoint** ``` -POST /api/v1/daily-updates/:sprintId +POST /api/v1/daily-scrums/:sprintId ``` **Authentication** @@ -247,12 +197,21 @@ POST /api/v1/daily-updates/:sprintId ```json { - "yesterdayWork": "string (optional, max 2000 chars)", - "todayWork": "string (optional, max 2000 chars)", - "impediment": "string (optional, max 2000 chars)" + "progressNotes": "string (optional, max 2000 chars)", + "adaptationsNotes": "string (optional, max 2000 chars)", + "planForNextDay": "string (optional, max 2000 chars)", + "focusMode": "'goal' | 'backlog' | 'impediment' | 'pair' (optional, null to clear)", + "backlogAdjustments": [ + { + "sprintBacklogItemId": "string (required, item UUID)", + "action": "string (required, max 500 chars)" + } + ] } ``` +All fields are optional because the Developers choose the structure. The creator is automatically recorded as a participant. + **Success Response** ```http @@ -264,41 +223,25 @@ Content-Type: application/json "data": { "id": "550e8400-e29b-41d4-a716-446655440010", "sprintId": "550e8400-e29b-41d4-a716-446655440000", - "userId": "550e8400-e29b-41d4-a716-446655440001", - "updateDate": "2026-05-10", - "yesterdayWork": "Completed login page", - "todayWork": "Working on dashboard", - "impediment": null, - "createdAt": "2026-05-10T09:00:00.000Z", - "createdBy": "550e8400-e29b-41d4-a716-446655440001", - "updatedAt": "2026-05-10T09:00:00.000Z", - "updatedBy": "550e8400-e29b-41d4-a716-446655440001", - "user": { - "id": "550e8400-e29b-41d4-a716-446655440001", - "firstName": "John", - "lastName": "Doe", - "email": "john@example.com" - } + "scrumDate": "2026-05-10", + "progressNotes": "On track toward the Sprint Goal", + "planForNextDay": "Pair on feature Y", + "participants": [], + "backlogAdjustments": [] } } ``` **Error Responses** -**400 Bad Request - Validation Error** +**409 Conflict - Record Already Exists** ```json { "success": false, "error": { - "code": "VALIDATION_ERROR", - "message": "Validation failed", - "details": [ - { - "field": "yesterdayWork", - "message": "yesterdayWork must be at most 2000 characters" - } - ] + "code": "CONFLICT", + "message": "A Daily Scrum already exists for today. Please edit the existing record." } } ``` @@ -315,41 +258,16 @@ Content-Type: application/json } ``` -**409 Conflict - Update Already Exists** - -```json -{ - "success": false, - "error": { - "code": "CONFLICT", - "message": "Daily update already exists for today. Please edit your existing update." - } -} -``` - -**Example Request** - -```bash -curl -X POST https://api.scrumooth.dev/api/v1/daily-updates/550e8400-e29b-41d4-a716-446655440000 \ - -H "Content-Type: application/json" \ - -b cookies.txt \ - -d '{ - "yesterdayWork": "Completed login page and code review", - "todayWork": "Working on dashboard component", - "impediment": "Waiting for API endpoint from backend team" - }' -``` - --- -### Get Daily Update by ID +### Get Daily Scrum by ID -Get a single daily update by its unique identifier. +Get a single team-level Daily Scrum by its identifier. **Endpoint** ``` -GET /api/v1/daily-updates/update/:id +GET /api/v1/daily-scrums/record/:id ``` **Authentication** @@ -358,7 +276,7 @@ GET /api/v1/daily-updates/update/:id **Path Parameters** -- `id` (string, required): Daily update UUID +- `id` (string, required): Daily Scrum UUID **Success Response** @@ -371,76 +289,40 @@ Content-Type: application/json "data": { "id": "550e8400-e29b-41d4-a716-446655440010", "sprintId": "550e8400-e29b-41d4-a716-446655440000", - "userId": "550e8400-e29b-41d4-a716-446655440001", - "updateDate": "2026-05-10", - "yesterdayWork": "Completed login page", - "todayWork": "Working on dashboard", - "impediment": null, - "createdAt": "2026-05-10T09:00:00.000Z", - "createdBy": "550e8400-e29b-41d4-a716-446655440001", - "updatedAt": "2026-05-10T09:00:00.000Z", - "updatedBy": "550e8400-e29b-41d4-a716-446655440001", - "user": { - "id": "550e8400-e29b-41d4-a716-446655440001", - "firstName": "John", - "lastName": "Doe", - "email": "john@example.com" - } + "scrumDate": "2026-05-10", + "progressNotes": "On track toward the Sprint Goal", + "adaptationsNotes": null, + "planForNextDay": "Pair on feature Y", + "focusMode": "goal", + "participants": [], + "backlogAdjustments": [] } } ``` -**Error Responses** - -**400 Bad Request - Invalid ID** - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "Invalid daily update ID" - } -} -``` - -**Example Request** - -```bash -curl -X GET https://api.scrumooth.dev/api/v1/daily-updates/update/550e8400-e29b-41d4-a716-446655440010 \ - -b cookies.txt -``` - --- -### Update Daily Update +### Update Daily Scrum -Update an existing daily update. Only the author of the update can edit it. +Update the team-level Daily Scrum by ID. **Endpoint** ``` -PUT /api/v1/daily-updates/update/:id +PUT /api/v1/daily-scrums/record/:id ``` **Authentication** - Required -- Only the update author can modify the update **Path Parameters** -- `id` (string, required): Daily update UUID +- `id` (string, required): Daily Scrum UUID **Request Body** -```json -{ - "yesterdayWork": "string (optional, max 2000 chars)", - "todayWork": "string (optional, max 2000 chars)", - "impediment": "string (optional, max 2000 chars)" -} -``` +Same shape as the create request body (all optional). **Success Response** @@ -453,84 +335,36 @@ Content-Type: application/json "data": { "id": "550e8400-e29b-41d4-a716-446655440010", "sprintId": "550e8400-e29b-41d4-a716-446655440000", - "userId": "550e8400-e29b-41d4-a716-446655440001", - "updateDate": "2026-05-10", - "yesterdayWork": "Completed login page and fixed bugs", - "todayWork": "Working on dashboard and API integration", - "impediment": "Waiting for API endpoint from backend team", - "createdAt": "2026-05-10T09:00:00.000Z", - "createdBy": "550e8400-e29b-41d4-a716-446655440001", - "updatedAt": "2026-05-10T10:30:00.000Z", - "updatedBy": "550e8400-e29b-41d4-a716-446655440001", - "user": { - "id": "550e8400-e29b-41d4-a716-446655440001", - "firstName": "John", - "lastName": "Doe", - "email": "john@example.com" - } + "scrumDate": "2026-05-10", + "progressNotes": "On track toward the Sprint Goal", + "planForNextDay": "Adapted plan", + "participants": [], + "backlogAdjustments": [] } } ``` -**Error Responses** - -**400 Bad Request - Not the Author** - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "You can only edit your own daily updates" - } -} -``` - -**404 Not Found - Update Not Found** - -```json -{ - "success": false, - "error": { - "code": "NOT_FOUND", - "message": "Daily update not found" - } -} -``` - -**Example Request** - -```bash -curl -X PUT https://api.scrumooth.dev/api/v1/daily-updates/update/550e8400-e29b-41d4-a716-446655440010 \ - -H "Content-Type: application/json" \ - -b cookies.txt \ - -d '{ - "yesterdayWork": "Completed login page and fixed bugs", - "todayWork": "Working on dashboard and API integration", - "impediment": "Waiting for API endpoint from backend team" - }' -``` +When `backlogAdjustments` is provided, the existing adjustments are replaced wholesale. --- -### Delete Daily Update +### Record Participation -Delete a daily update. Only the author of the update can delete it. +Record the current user as a participant of a team-level Daily Scrum. This reflects contribution without creating a per-user report. **Endpoint** ``` -DELETE /api/v1/daily-updates/update/:id +POST /api/v1/daily-scrums/record/:id/participate ``` **Authentication** - Required -- Only the update author can delete the update **Path Parameters** -- `id` (string, required): Daily update UUID +- `id` (string, required): Daily Scrum UUID **Success Response** @@ -540,55 +374,20 @@ Content-Type: application/json { "success": true, - "data": { - "message": "Daily update deleted successfully" - } + "data": { "id": "550e8400-e29b-41d4-a716-446655440010" } } ``` -**Error Responses** - -**400 Bad Request - Not the Author** - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "You can only delete your own daily updates" - } -} -``` - -**404 Not Found - Update Not Found** - -```json -{ - "success": false, - "error": { - "code": "NOT_FOUND", - "message": "Daily update not found" - } -} -``` - -**Example Request** - -```bash -curl -X DELETE https://api.scrumooth.dev/api/v1/daily-updates/update/550e8400-e29b-41d4-a716-446655440010 \ - -b cookies.txt -``` - --- -### Promote Impediment +### Get Participation -Promote an impediment from a daily update into a formal impediment record. The daily update must contain impediment text and must not already have an associated impediment record. +Get participation for a sprint on a date, without status-report framing. **Endpoint** ``` -POST /api/v1/daily-updates/:id/promote-impediment +GET /api/v1/daily-scrums/:sprintId/participation ``` **Authentication** @@ -597,192 +396,42 @@ POST /api/v1/daily-updates/:id/promote-impediment **Path Parameters** -- `id` (string, required): Daily update UUID +- `sprintId` (string, required): Sprint UUID -**Request Body** +**Query Parameters** -```json -{ - "title": "string (required, 3-200 chars)", - "description": "string (required, 10-2000 chars)", - "ownerId": "string (optional, user UUID)", - "priority": "string (optional, one of: High, Medium, Low)", - "teamId": "string (required, team UUID)", - "sprintId": "string (optional, sprint UUID)" -} -``` +- `date` (string, required): Date in YYYY-MM-DD format **Success Response** ```http -HTTP/1.1 201 Created +HTTP/1.1 200 OK Content-Type: application/json { "success": true, "data": { - "dailyUpdate": { - "id": "550e8400-e29b-41d4-a716-446655440010", - "sprintId": "550e8400-e29b-41d4-a716-446655440000", - "userId": "550e8400-e29b-41d4-a716-446655440001", - "updateDate": "2026-05-10", - "yesterdayWork": "Completed login page", - "todayWork": "Working on dashboard", - "impediment": "Waiting for API endpoint from backend team", - "createdAt": "2026-05-10T09:00:00.000Z", - "createdBy": "550e8400-e29b-41d4-a716-446655440001", - "updatedAt": "2026-05-10T09:00:00.000Z", - "updatedBy": "550e8400-e29b-41d4-a716-446655440001", - "user": { - "id": "550e8400-e29b-41d4-a716-446655440001", - "firstName": "John", - "lastName": "Doe", - "email": "john@example.com" - }, - "impedimentRecord": { - "id": "550e8400-e29b-41d4-a716-446655440020", - "status": "OPEN", - "title": "API dependency blocking dashboard work", - "reportedBy": { - "id": "550e8400-e29b-41d4-a716-446655440001", - "firstName": "John", - "lastName": "Doe" - }, - "owner": null - } - }, - "impediment": { - "id": "550e8400-e29b-41d4-a716-446655440020", - "teamId": "550e8400-e29b-41d4-a716-446655440099", - "sprintId": "550e8400-e29b-41d4-a716-446655440000", - "title": "API dependency blocking dashboard work", - "description": "The backend API endpoint for dashboard data is not yet available, blocking frontend dashboard development.", - "reportedById": "550e8400-e29b-41d4-a716-446655440001", - "ownerId": null, - "status": "OPEN", - "resolution": null, - "resolvedAt": null, - "dailyUpdateId": "550e8400-e29b-41d4-a716-446655440010", - "createdAt": "2026-05-10T10:00:00.000Z", - "createdBy": "550e8400-e29b-41d4-a716-446655440001", - "updatedAt": "2026-05-10T10:00:00.000Z", - "updatedBy": null, - "reportedBy": { - "id": "550e8400-e29b-41d4-a716-446655440001", - "firstName": "John", - "lastName": "Doe", - "email": "john@example.com" - }, - "owner": null, - "sprint": { - "id": "550e8400-e29b-41d4-a716-446655440000", - "name": "Sprint 5" - }, - "dailyUpdate": { - "id": "550e8400-e29b-41d4-a716-446655440010", - "sprintId": "550e8400-e29b-41d4-a716-446655440000", - "userId": "550e8400-e29b-41d4-a716-446655440001", - "updateDate": "2026-05-10", - "yesterdayWork": "Completed login page", - "todayWork": "Working on dashboard", - "impediment": "Waiting for API endpoint from backend team", - "createdAt": "2026-05-10T09:00:00.000Z", - "createdBy": "550e8400-e29b-41d4-a716-446655440001", - "updatedAt": "2026-05-10T09:00:00.000Z", - "updatedBy": "550e8400-e29b-41d4-a716-446655440001", - "user": { - "id": "550e8400-e29b-41d4-a716-446655440001", - "firstName": "John", - "lastName": "Doe", - "email": "john@example.com" - } - } - } - } -} -``` - -**Error Responses** - -**400 Bad Request - No Impediment Text** - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "No impediment text to promote" - } -} -``` - -**400 Bad Request - Already Promoted** - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "Impediment already created from this daily update" - } -} -``` - -**400 Bad Request - Validation Error** - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "Validation failed", - "details": [ - { - "field": "title", - "message": "Title must be at least 3 characters" - } + "dailyScrum": { "id": "550e8400-e29b-41d4-a716-446655440010" }, + "participants": [ + { "id": "550e8400-e29b-41d4-a716-446655440030", "userId": "550e8400-e29b-41d4-a716-446655440001", "userName": "John Doe" } + ], + "nonParticipants": [ + { "userId": "550e8400-e29b-41d4-a716-446655440002", "userName": "Jane Smith" } ] } } ``` -**404 Not Found - Daily Update Not Found** - -```json -{ - "success": false, - "error": { - "code": "NOT_FOUND", - "message": "Daily update not found" - } -} -``` - -**Example Request** - -```bash -curl -X POST https://api.scrumooth.dev/api/v1/daily-updates/550e8400-e29b-41d4-a716-446655440010/promote-impediment \ - -H "Content-Type: application/json" \ - -b cookies.txt \ - -d '{ - "title": "API dependency blocking dashboard work", - "description": "The backend API endpoint for dashboard data is not yet available, blocking frontend dashboard development.", - "teamId": "550e8400-e29b-41d4-a716-446655440099", - "sprintId": "550e8400-e29b-41d4-a716-446655440000", - "priority": "High" - }' -``` - --- -### Send Daily Update Reminder +### Send Team-Wide Signal -Send a daily update reminder notification to team members who have not yet submitted their update for the current day. +Send a neutral team-wide Daily Scrum signal to gather the Developers who have **not yet joined** today's Daily Scrum. Because the Daily Scrum is a Developers-only event (Scrum Guide), the signal targets only non-joined Developers — Product Owner and Scrum Master are excluded, as are Developers who have already joined. It does not demand an individual report from anyone. **Endpoint** ``` -POST /api/v1/daily-updates/:sprintId/send-reminder +POST /api/v1/daily-scrums/:sprintId/team-signal ``` **Authentication** @@ -803,68 +452,67 @@ Content-Type: application/json "success": true, "data": { "sentCount": 2, - "totalPending": 2, - "message": "Reminders sent to 2 team members" + "message": "Daily Scrum signal sent to 2 team members" } } ``` -**All Members Submitted Response** +**Notes** -```http -HTTP/1.1 200 OK -Content-Type: application/json +- `sentCount` is the number of Developers who have not yet joined the Daily Scrum for today. +- The Product Owner, Scrum Master, and Developers who already joined do not receive the signal. -{ - "success": true, - "data": { - "sentCount": 0, - "message": "All team members have submitted their updates" - } -} -``` +--- -**Partial Failure Response** +### Promote Impediment from Daily Scrum -```http -HTTP/1.1 200 OK -Content-Type: application/json +Promote an impediment raised in a Daily Scrum into a formal Impediment record. -{ - "success": true, - "data": { - "sentCount": 1, - "totalPending": 2, - "message": "Reminders sent to 1 team member", - "errors": [ - "Failed to send reminder to Jane Smith" - ] - } -} +**Endpoint** + +``` +POST /api/v1/daily-scrums/:id/promote-impediment ``` -**Error Responses** +**Authentication** -**400 Bad Request - Sprint Not Found** +- Required + +**Path Parameters** + +- `id` (string, required): Daily Scrum UUID + +**Request Body** ```json { - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "Sprint not found" - } + "title": "string (required, 3-200 chars)", + "description": "string (required, 10-2000 chars)", + "ownerId": "string (optional, user UUID)", + "priority": "string (optional, one of: High, Medium, Low)", + "teamId": "string (required, team UUID)", + "sprintId": "string (optional, sprint UUID)" } ``` -**Example Request** +**Success Response** -```bash -curl -X POST https://api.scrumooth.dev/api/v1/daily-updates/550e8400-e29b-41d4-a716-446655440000/send-reminder \ - -b cookies.txt -``` +```http +HTTP/1.1 201 Created +Content-Type: application/json ---- +{ + "success": true, + "data": { + "dailyScrum": { "id": "550e8400-e29b-41d4-a716-446655440010" }, + "impediment": { + "id": "550e8400-e29b-41d4-a716-446655440020", + "title": "API access blocked", + "status": "OPEN" + } + } +} +``` ## Error Codes diff --git a/docs/api/impediments.md b/docs/api/impediments.md index 35ef9f09..1aff90e7 100644 --- a/docs/api/impediments.md +++ b/docs/api/impediments.md @@ -107,7 +107,6 @@ Content-Type: application/json "status": "IN_PROGRESS", "resolution": null, "resolvedAt": null, - "dailyUpdateId": null, "createdAt": "2026-05-10T10:00:00.000Z", "createdBy": "550e8400-e29b-41d4-a716-446655440001", "updatedAt": "2026-05-10T11:00:00.000Z", @@ -127,8 +126,7 @@ Content-Type: application/json "sprint": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Sprint 5" - }, - "dailyUpdate": null + } } ] } @@ -256,7 +254,6 @@ Content-Type: application/json "status": "IN_PROGRESS", "resolution": null, "resolvedAt": null, - "dailyUpdateId": null, "createdAt": "2026-05-10T10:00:00.000Z", "createdBy": "550e8400-e29b-41d4-a716-446655440001", "updatedAt": "2026-05-10T11:00:00.000Z", @@ -276,8 +273,7 @@ Content-Type: application/json "sprint": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Sprint 5" - }, - "dailyUpdate": null + } } } ``` @@ -362,7 +358,6 @@ Content-Type: application/json "status": "OPEN", "resolution": null, "resolvedAt": null, - "dailyUpdateId": null, "createdAt": "2026-05-10T10:00:00.000Z", "createdBy": "550e8400-e29b-41d4-a716-446655440001", "updatedAt": "2026-05-10T10:00:00.000Z", @@ -382,8 +377,7 @@ Content-Type: application/json "sprint": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Sprint 5" - }, - "dailyUpdate": null + } } } ``` @@ -467,7 +461,6 @@ Content-Type: application/json "status": "RESOLVED", "resolution": "Backend API endpoint deployed and verified. Frontend integration complete.", "resolvedAt": "2026-05-11T14:00:00.000Z", - "dailyUpdateId": null, "createdAt": "2026-05-10T10:00:00.000Z", "createdBy": "550e8400-e29b-41d4-a716-446655440001", "updatedAt": "2026-05-11T14:00:00.000Z", @@ -487,8 +480,7 @@ Content-Type: application/json "sprint": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Sprint 5" - }, - "dailyUpdate": null + } } } ``` diff --git a/docs/api/teams.md b/docs/api/teams.md index c03b8640..18d5585c 100644 --- a/docs/api/teams.md +++ b/docs/api/teams.md @@ -533,7 +533,7 @@ POST /api/v1/teams/:teamId/members ```json { "email": "string (required, valid email)", - "role": "string (required, one of: PRODUCT_OWNER, SCRUM_MASTER, DEVELOPER)" + "role": "string (required, one of: PRODUCT_OWNER, SCRUM_MASTER, DEVELOPERS)" } ``` @@ -550,7 +550,7 @@ Content-Type: application/json "id": "550e8400-e29b-41d4-a716-446655440003", "userId": "550e8400-e29b-41d4-a716-446655440004", "teamId": "550e8400-e29b-41d4-a716-446655440000", - "role": "DEVELOPER", + "role": "DEVELOPERS", "joinedAt": "2026-04-29T14:00:00.000Z", "user": { "id": "550e8400-e29b-41d4-a716-446655440004", @@ -590,6 +590,20 @@ Content-Type: application/json } ``` +**409 Conflict - Team Size Limit Reached** + +The Scrum Team has reached its configured maximum number of members (default `10`, configurable via `TEAM_MAX_SIZE`). No additional members can be added. + +```json +{ + "success": false, + "error": { + "code": "TEAM_SIZE_LIMIT_REACHED", + "message": "This team has reached the maximum size of 10 members. No additional members can be added." + } +} +``` + **Example Request** ```bash @@ -598,7 +612,7 @@ curl -X POST https://api.scrumooth.dev/api/v1/teams/550e8400-e29b-41d4-a716-4466 -b cookies.txt \ -d '{ "email": "developer@example.com", - "role": "DEVELOPER" + "role": "DEVELOPERS" }' ``` @@ -697,7 +711,7 @@ PUT /api/v1/teams/:teamId/members/:memberId ```json { - "role": "string (required, one of: PRODUCT_OWNER, SCRUM_MASTER, DEVELOPER)" + "role": "string (required, one of: PRODUCT_OWNER, SCRUM_MASTER, DEVELOPERS)" } ``` @@ -846,13 +860,14 @@ For Definition of Done and Definition of Ready endpoints, see: ## Error Codes -| Code | HTTP Status | Description | -| ---------------------- | ----------- | ---------------------------------------------------------- | -| `VALIDATION_ERROR` | 400 | Request validation failed | -| `AUTHENTICATION_ERROR` | 401 | Authentication required | -| `AUTHORIZATION_ERROR` | 403 | Insufficient permissions | -| `NOT_FOUND` | 404 | Team or member not found | -| `CONFLICT` | 409 | Resource conflict (e.g., duplicate name, already a member) | +| Code | HTTP Status | Description | +| ------------------------- | ----------- | ---------------------------------------------------------- | +| `VALIDATION_ERROR` | 400 | Request validation failed | +| `AUTHENTICATION_ERROR` | 401 | Authentication required | +| `AUTHORIZATION_ERROR` | 403 | Insufficient permissions | +| `NOT_FOUND` | 404 | Team or member not found | +| `CONFLICT` | 409 | Resource conflict (e.g., duplicate name, already a member) | +| `TEAM_SIZE_LIMIT_REACHED` | 409 | Team has reached its maximum member size | ## Best Practices diff --git a/docs/architecture/api-specifications.md b/docs/architecture/api-specifications.md index 0c1f2633..545a2509 100644 --- a/docs/architecture/api-specifications.md +++ b/docs/architecture/api-specifications.md @@ -65,7 +65,7 @@ Workflows → /workflows/* Sprints → /sprints/* Sprint Backlog → /sprint-backlog/* (shared with sprint routes) Sprint Config → /sprint-configuration/* -Daily Updates → /daily-updates/* +Daily Scrums → /daily-scrums/* Increments → /increments/* Sprint Reviews → /sprint-reviews/* Retrospectives → /retrospectives/* diff --git a/docs/architecture/component-design.md b/docs/architecture/component-design.md index 5a42ece1..8bc9a564 100644 --- a/docs/architecture/component-design.md +++ b/docs/architecture/component-design.md @@ -233,8 +233,7 @@ pages/ # Page-scoped feature components │ │ ├── TaskCard.tsx │ │ ├── SwimlanesBoard.tsx │ │ ├── SprintBoardHeader.tsx -│ │ ├── BurndownChart.tsx -│ │ └── DoDVerificationModal.tsx +│ │ └── BurndownChart.tsx │ └── utils/ # Form validation and change detection ├── SprintPlanning/ │ └── components/ # AddTaskModal, StartSprintModal, etc. @@ -527,7 +526,7 @@ The data flow follows a strict layered pattern from API services through TanStac | `sprintBacklogService` | Sprint Backlog | Add/remove PBIs, track changes | | `productBacklogService` | Product Backlog | CRUD, prioritization, MoSCoW | | `productGoalsService` | Product Goals | CRUD, status transitions | -| `dailyUpdatesService` | Daily Scrum | Create, update daily updates | +| `dailyScrumService` | Daily Scrum | Create, inspect & adapt the Daily Scrum | | `impedimentsService` | Impediments | Create, resolve, assign | | `reportsService` | Reports | Velocity, burndown, sprint metrics | | `definitionService` | Definitions | DoD/DoR checklist management | @@ -1128,7 +1127,7 @@ The backend follows a layered architecture where controllers handle HTTP concern | `sprint.service.ts` | Sprints | Lifecycle, planning, completion | | `backlog.service.ts` | Product Backlog | CRUD, prioritization, MoSCoW | | `goals.service.ts` | Product Goals | CRUD, status transitions | -| `dailyUpdate.service.ts` | Daily Scrum | Daily update creation and retrieval | +| `dailyScrum.service.ts` | Daily Scrum | Inspect & adapt, participation | | `impediment.service.ts` | Impediments | Create, resolve, assign | | `reports.service.ts` | Reports | Velocity, burndown calculations | | `dod.service.ts` | Definition of Done | DoD checklist management | @@ -1201,17 +1200,17 @@ packages/shared/ Core domain types defined in `packages/shared/src/types/index.ts`: -| Type | Purpose | -| ------------------- | -------------------------------------------- | -| `User` | User entity with role and profile data | -| `UserRole` | Enum: PRODUCT_OWNER, SCRUM_MASTER, DEVELOPER | -| `UserSession` | Session data with userId, email, role | -| `Team` | Team entity with name and description | -| `Sprint` | Sprint entity with dates and status | -| `SprintStatus` | Enum: PLANNED, ACTIVE, COMPLETED, CANCELLED | -| `BacklogItem` | Backlog item with priority and story points | -| `BacklogItemStatus` | Enum: NEW, REFINED, READY, IN_PROGRESS, DONE | -| `Task` | Task entity linked to backlog item | +| Type | Purpose | +| ------------------- | --------------------------------------------- | +| `User` | User entity with role and profile data | +| `UserRole` | Enum: PRODUCT_OWNER, SCRUM_MASTER, DEVELOPERS | +| `UserSession` | Session data with userId, email, role | +| `Team` | Team entity with name and description | +| `Sprint` | Sprint entity with dates and status | +| `SprintStatus` | Enum: PLANNED, ACTIVE, COMPLETED, CANCELLED | +| `BacklogItem` | Backlog item with priority and story points | +| `BacklogItemStatus` | Enum: NEW, REFINED, READY, IN_PROGRESS, DONE | +| `Task` | Task entity linked to backlog item | ### Shared Constants diff --git a/docs/architecture/data-model.md b/docs/architecture/data-model.md index c4a34ee5..0bbd4f7b 100644 --- a/docs/architecture/data-model.md +++ b/docs/architecture/data-model.md @@ -139,17 +139,17 @@ The database schema is organized into logical groups: │ SPRINT MANAGEMENT │ ├──────────────────────────────────────────────────────────────────────┤ │ │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Sprint │◄────────│ Task │ │ DailyUpdate │ │ -│ ├──────────────┤ 1:N ├──────────────┤ ├──────────────┤ │ -│ │ id (PK) │ │ id (PK) │ │ id (PK) │ │ -│ │ teamId (FK) │ │ sprintId (FK)│ │ sprintId (FK)│ │ -│ │ goalId (FK) │ │ pbiId (FK) │ │ userId (FK) │ │ -│ │ name │ │ title │ │ updateDate │ │ -│ │ startDate │ │ assigneeId │ │ yesterdayWork│ │ -│ │ endDate │ │ status │ │ todayWork │ │ -│ │ sprintGoal │ │ estimatedHrs │ │ impediment │ │ -│ │ status │ │ remainingHrs │ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ Sprint │◄────────│ Task │ │ +│ ├──────────────┤ 1:N ├──────────────┤ │ +│ │ id (PK) │ │ id (PK) │ │ +│ │ teamId (FK) │ │ sprintId (FK)│ │ +│ │ goalId (FK) │ │ pbiId (FK) │ │ +│ │ name │ │ title │ │ +│ │ startDate │ │ assigneeId │ │ +│ │ endDate │ │ status │ │ +│ │ sprintGoal │ │ estimatedHrs │ │ +│ │ status │ │ remainingHrs │ │ │ └──────────────┘ └──────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ @@ -245,7 +245,7 @@ The database schema is organized into logical groups: - `PRODUCT_OWNER`: Product backlog management - `SCRUM_MASTER`: Sprint and team management -- `DEVELOPER`: Task execution +- `DEVELOPERS`: Task execution **Indexes**: @@ -437,7 +437,7 @@ Team (1) ──► (N) ProductGoal Team (1) ──► (N) Sprint ProductGoal (1) ──► (N) ProductBacklogItem Sprint (1) ──► (N) Task -Sprint (1) ──► (N) DailyUpdate +Sprint (1) ──► (N) DailyScrum ``` ### Many-to-Many Relationships @@ -489,7 +489,6 @@ CREATE INDEX idx_sessions_user_activity ON refresh_tokens(userId, lastActivityAt -- Date-based queries CREATE INDEX idx_sprint_dates ON sprints(startDate, endDate); -CREATE INDEX idx_daily_updates_date ON daily_updates(updateDate); ``` ### Query Optimization Examples diff --git a/docs/architecture/security-architecture.md b/docs/architecture/security-architecture.md index a3e1cc27..9bf37179 100644 --- a/docs/architecture/security-architecture.md +++ b/docs/architecture/security-architecture.md @@ -163,7 +163,7 @@ Scrumooth implements a three-role RBAC model with hierarchical permissions: │ └──────────┬───────────┘ │ │ │ inherits │ │ ┌──────────▼───────────┐ │ -│ │ DEVELOPER │ │ +│ │ DEVELOPERS │ │ │ │ - Task updates │ │ │ │ - View boards │ │ │ │ - Personal profile │ │ @@ -298,6 +298,8 @@ const createTeamSchema = z.object({ }); ``` +In addition to request-body schema validation, business-rule limits are enforced in the service layer. For example, `TeamService.addMember()` validates that a team has not reached its configured maximum size (default `10`, configurable via `TEAM_MAX_SIZE`) before creating a membership, returning `409 TEAM_SIZE_LIMIT_REACHED` when the team is at capacity. This prevents arbitrarily large Scrum Teams and aligns with the Scrum Guide recommendation. + ### SQL Injection Prevention SQL injection is prevented through Prisma ORM's parameterized queries. All database access goes through Prisma, which: diff --git a/docs/screenshots/scrumooth.png b/docs/screenshots/scrumooth.png index 75bd7c5b..d2546510 100644 Binary files a/docs/screenshots/scrumooth.png and b/docs/screenshots/scrumooth.png differ diff --git a/docs/screenshots/scrumooth_de.png b/docs/screenshots/scrumooth_de.png index 305d1419..ad447c51 100644 Binary files a/docs/screenshots/scrumooth_de.png and b/docs/screenshots/scrumooth_de.png differ diff --git a/docs/screenshots/scrumooth_es.png b/docs/screenshots/scrumooth_es.png index ef3b8e59..6730c695 100644 Binary files a/docs/screenshots/scrumooth_es.png and b/docs/screenshots/scrumooth_es.png differ diff --git a/docs/screenshots/scrumooth_fr.png b/docs/screenshots/scrumooth_fr.png index 4eeb1427..4e11d78e 100644 Binary files a/docs/screenshots/scrumooth_fr.png and b/docs/screenshots/scrumooth_fr.png differ diff --git a/docs/screenshots/scrumooth_it.png b/docs/screenshots/scrumooth_it.png index 9fc01d8b..68396636 100644 Binary files a/docs/screenshots/scrumooth_it.png and b/docs/screenshots/scrumooth_it.png differ diff --git a/docs/user-guide/core-features/daily-scrum.md b/docs/user-guide/core-features/daily-scrum.md index 622b7615..357df59d 100644 --- a/docs/user-guide/core-features/daily-scrum.md +++ b/docs/user-guide/core-features/daily-scrum.md @@ -1,13 +1,13 @@ # Daily Scrum -The Daily Scrum is a 15-minute time-boxed event for the Development Team to synchronize activities and create a plan for the next 24 hours. +The Daily Scrum is a 15-minute time-boxed event **for the Developers**. Its purpose is to inspect progress toward the Sprint Goal and adapt the Sprint Backlog, producing an actionable plan for the next day of work. It is **not** a per-user status report to management. ## Table of Contents - [Overview](#overview) - [Conducting the Daily Scrum](#conducting-the-daily-scrum) -- [Providing Updates](#providing-updates) -- [Viewing Team Updates](#viewing-team-updates) +- [Recording the Daily Scrum](#recording-the-daily-scrum) +- [Viewing the Team Record](#viewing-the-team-record) - [Identifying Impediments](#identifying-impediments) - [Best Practices](#best-practices) - [Common Anti-Patterns](#common-anti-patterns) @@ -21,8 +21,8 @@ The Daily Scrum is a 15-minute time-boxed event for the Development Team to sync The Daily Scrum is used to: 1. **Inspect** progress toward the Sprint Goal -2. **Synchronize** team activities -3. **Plan** the next 24 hours of work +2. **Adapt** the Sprint Backlog based on that progress +3. **Produce** an actionable plan for the next day 4. **Identify** impediments ### What It Is NOT @@ -33,16 +33,19 @@ The Daily Scrum is **not**: - A problem-solving session - A management reporting tool - A lengthy discussion +- A per-user "yesterday / today / blockers" submission ### Key Characteristics -| Aspect | Guideline | -| ---------------- | ------------------------------------------- | -| **Duration** | Maximum 15 minutes | -| **Frequency** | Every working day | -| **Participants** | Development Team (Scrum Master facilitates) | -| **Location** | Same place and time each day | -| **Format** | Each member answers three questions | +| Aspect | Guideline | +| ---------------- | ------------------------------------- | +| **Duration** | Maximum 15 minutes | +| **Frequency** | Every working day | +| **Participants** | Developers (Scrum Master facilitates) | +| **Location** | Same place and time each day | +| **Format** | Developers choose the structure | + +The Developers can select whatever structure and techniques they want, as long as the Daily Scrum focuses on progress toward the Sprint Goal and produces an actionable plan. Scrumooth supports this by letting the team choose its focus rather than mandating a fixed set of questions. --- @@ -52,135 +55,104 @@ The Daily Scrum is **not**: 1. Click "Daily Scrum" in the sidebar 2. The Daily Scrum interface displays: - - Today's date - - Team members - - Update form - - Previous updates - -### The Three Questions - -Each team member answers: + - The **Sprint Goal** as the primary anchor + - The date selector + - The team-level Daily Scrum record for the selected date + - Goal-relevant metrics (goal progress, backlog items adjusted, participants) -1. **What did I do yesterday** that helped meet the Sprint Goal? -2. **What will I do today** to help meet the Sprint Goal? -3. **Do I see any impediment** that prevents me or the team from meeting the Sprint Goal? +### Team-Level Record -### Daily Process +The Daily Scrum is stored as a single **team-level record** per Sprint per day, jointly owned by the Developers. There is no per-user status report. -1. **Team gathers** (same time each day) -2. **Each member provides update**: - - Briefly answer the three questions - - Focus on Sprint Goal contribution - - Raise impediments clearly +Because the Daily Scrum is an event **for the Developers** (Scrum Guide), only team members with the **Developer** role can record or edit the shared inspect/adapt/plan content. The Product Owner and Scrum Master may attend and observe the record, but they cannot author or modify it — this keeps the Developers' plan self-managed. A non-Developer viewing the page sees a read-only notice instead of the record/edit actions. -3. **Scrum Master notes impediments** -4. **Meeting ends** within 15 minutes -5. **Follow-up discussions** happen after (separate meetings) +### Developer-Chosen Structure ---- - -## Providing Updates +The Developers decide how to run the Daily Scrum. Scrumooth offers a **"choose your focus"** selector with non-mandatory modes: -### Entering Your Update +- **Goal progress** — inspect progress toward the Sprint Goal +- **Sprint Backlog walk** — review and adapt the Sprint Backlog +- **Impediment-first** — surface blockers first +- **Pair-up plan** — plan pairing for the next day -1. **Navigate to Daily Scrum page** -2. **Find your update form** for today -3. **Fill in the three sections**: +The focus selector is **part of the Inspect & Adapt edit form**, so only a Developer can choose the structure while recording. The chosen focus is saved with the shared team record and displayed as a read-only badge on the record view — so the whole team (including the Product Owner and Scrum Master) can see how the Daily Scrum is being run. - **Yesterday's Work:** +--- - ``` - Completed user authentication API endpoints - Fixed login page styling issue - Reviewed Carol's PR for password reset - ``` +## Recording the Daily Scrum - **Today's Plan:** +### Starting the Record - ``` - Implement password reset email functionality - Write unit tests for auth service - Start on user profile page - ``` +On the current day, when no record exists yet, Scrumooth opens the Inspect & Adapt form. - **Impediments:** +### The Inspect & Adapt Form - ``` - Waiting for design assets from UX team - Staging environment is down (reported to DevOps) - ``` +The form captures the Daily Scrum's output as a shared team record: -4. **Save your update** +- **Progress toward Sprint Goal** — how is the team progressing toward the goal? +- **Adaptations (Sprint Backlog)** — what adjustments to the Sprint Backlog were agreed? +- **Sprint Backlog adjustments (optional)** — link adjustments to specific Sprint Backlog items +- **Plan for next day** — the actionable plan the Developers agreed for the next day -### Update Guidelines +``` +Progress toward Sprint Goal: + We completed the user authentication API; the checkout flow is ~80% done. -**Be Specific:** +Adaptations (Sprint Backlog): + Reassigned the password-reset item to Carol; moved profile page out of this sprint. -``` -❌ "Worked on stuff" -✅ "Completed API endpoint for user registration" +Plan for next day: + Carol and Bob pair on password-reset email; Alice starts performance testing. ``` -**Focus on Sprint Goal:** +4. **Save the record** — the team-level record is stored for the selected date. -``` -❌ "Had a meeting about next quarter" -✅ "Implemented checkout flow (supports sprint goal)" -``` +### Participation, Not Reporting -**Be Brief:** +The page shows who has **participated** in the Daily Scrum. This is a neutral signal to support self-management, **not** a list of who "owes" a status report. A team-wide signal can be sent to gather the Developers who have **not yet joined**, but it never demands an individual report from anyone. -- One to three items per section -- Save details for after the standup -- Focus on what's most relevant +Because the Daily Scrum is a Developers-only event, the **"Not yet joined"** list tracks **Developers** only. The Product Owner and Scrum Master attend to observe but are not expected to "join" or author the record, so they never appear as missing. The team-wide signal follows the same rule: it is sent only to the Developers who have not yet joined, so the notification count matches the "Not yet joined" card. --- -## Viewing Team Updates +## Viewing the Team Record -### Team Update View +### Team Record View -The Daily Scrum page shows all team updates: +The Daily Scrum page shows the shared team-level record: ``` ┌─────────────────────────────────────────────────────────┐ -│ Daily Scrum - January 15, 2026 │ +│ Daily Scrum - January 15, 2026 │ +│ Sprint Goal: Deliver the reporting module │ ├─────────────────────────────────────────────────────────┤ -│ │ -│ 👤 Alice │ -│ ✓ Yesterday: Auth API, login styling │ -│ → Today: Password reset email, unit tests │ -│ ⚠ Impediment: Waiting for UX assets │ -│ │ -│ 👤 Bob │ -│ ✓ Yesterday: Database optimization │ -│ → Today: Performance testing, code review │ -│ ⚠ Impediment: None │ -│ │ -│ 👤 Carol │ -│ ✓ Yesterday: PR review, bug fixes │ -│ → Today: User profile page │ -│ ⚠ Impediment: Staging environment down │ -│ │ +│ Inspect & Adapt │ +│ ● Progress toward Sprint Goal │ +│ We completed the auth API; checkout is ~80% done. │ +│ ● Adaptations (Sprint Backlog) │ +│ Reassigned password-reset to Carol. │ +│ ● Plan for next day │ +│ Carol + Bob pair on password reset; Alice tests. │ +│ 👥 Joined: Alice, Bob, Carol │ └─────────────────────────────────────────────────────────┘ ``` -### Update History +### History View previous days: -- Click date selector -- See historical updates -- Track patterns over time +- Click a date in the quick-date bar or the date selector +- See the team-level record for each day +- Track the team's progress over time -### Sprint Progress +### Goal-Relevant Metrics -The page also shows: +The page shows metrics that reflect **progress toward the Sprint Goal**, not report-completion counts: -- Sprint Goal reminder -- Days remaining in sprint -- Burndown chart snippet -- Active impediments count +- Sprint Goal progress % +- Sprint Backlog items adjusted +- Impediments raised +- Participants --- @@ -194,16 +166,6 @@ An impediment is anything that: - Reduces team effectiveness - Prevents meeting the Sprint Goal -### Common Impediments - -| Category | Examples | -| ------------- | ---------------------------------------- | -| **Technical** | Environment issues, bugs, technical debt | -| **Process** | Unclear requirements, approval delays | -| **Resource** | Missing tools, access, dependencies | -| **External** | Vendor delays, stakeholder availability | -| **Team** | Illness, conflicts, knowledge gaps | - ### Raising Impediments 1. **During Daily Scrum**: @@ -211,74 +173,40 @@ An impediment is anything that: - Indicate impact on work - Suggest resolution if known -2. **After Daily Scrum**: - - Scrum Master logs impediment formally - - Navigate to Impediments page - - Create impediment record +2. **Promote to a formal record**: + - Use "Create Impediment" to promote an impediment raised in the Daily Scrum into a formal Impediment record + - The formal record is tracked and can be assigned an owner and priority 3. **Track Resolution**: - Assign owner - Set target resolution date - Update status as progress is made -### Impediment Resolution - -The Scrum Master is responsible for: - -- Removing impediments -- Escalating when necessary -- Keeping the team informed of progress - --- ## Best Practices -### For Team Members - -**Before the Daily Scrum:** - -- Review your work from yesterday -- Know what you'll work on today -- Identify any blockers +### For the Developers -**During the Daily Scrum:** - -- Be on time -- Listen to others' updates -- Keep your update brief +- Start and end on time (max 15 minutes) +- Keep the focus on progress toward the Sprint Goal +- Agree on an actionable plan for the next day - Raise impediments clearly - -**After the Daily Scrum:** - -- Follow up on discussions -- Help resolve impediments -- Update the Sprint Board +- Adapt the Sprint Backlog when reality changes ### For Scrum Masters -**Facilitation:** - -- Start exactly on time -- Keep updates to 15 minutes -- Ensure everyone speaks +- Facilitate, do not dominate +- Help keep the event to 15 minutes - Note impediments for follow-up - -**Don't:** - -- Let discussions drag on -- Ask for detailed status reports -- Let one person dominate -- Skip the meeting +- Do **not** turn the event into a status report to management ### For Remote Teams -**Virtual Standup Tips:** - - Use video when possible -- Mute when not speaking -- Have a speaking order - Use a timer to stay on track -- Consider async updates for time zones +- Have a speaking order +- Consider async participation for time zones --- @@ -288,75 +216,32 @@ The Scrum Master is responsible for: | Anti-Pattern | Why It's a Problem | Solution | | ------------------------------ | --------------------- | ------------------------------- | -| **Status report to manager** | Not for management | Team-focused synchronization | +| **Status report to manager** | Not for management | Team-focused goal inspection | | **Problem solving in standup** | Takes too long | "Take it offline" | | **Skipping standup** | Loses synchronization | Make it a habit | -| **Long updates** | Exceeds time box | Be brief, one sentence per item | +| **Fixed three questions** | Limits team autonomy | Let Developers choose structure | | **No impediments raised** | Issues fester | Encourage openness | -| **Same time every day?** | Inconsistent | Fixed time and place | ### Signs of a Healthy Daily Scrum - ✅ Starts and ends on time - ✅ Everyone participates -- ✅ Focus on Sprint Goal +- ✅ Focus on the Sprint Goal - ✅ Impediments are raised -- ✅ Team self-organizes after +- ✅ Team self-organizes and adapts after ### Signs of an Unhealthy Daily Scrum - ❌ Regularly runs over 15 minutes - ❌ Only some people speak -- ❌ Detailed status reports +- ❌ Per-user status reports to management - ❌ No impediments ever raised - ❌ Management dominates --- -## Asynchronous Daily Scrum - -For distributed teams across time zones, Scrumooth supports async updates. - -### How It Works - -1. **Team members post updates** when their day starts -2. **Others read updates** before starting work -3. **Sync meeting** held for overlap period (optional) - -### Async Update Format - -``` -📅 Date: January 15, 2026 - -✅ Yesterday: -- Completed user auth API -- Fixed login styling - -→ Today: -- Password reset email -- Unit tests - -⚠ Impediments: -- Waiting for UX assets (ETA: today 2pm) -``` - -> **Note**: Asynchronous updates are submitted through the same Daily Scrum interface. Team members can submit their updates at any time during their working day. The Scrum Master can review all updates and identify impediments that need follow-up. - -### Reminder Feature - -Scrum Masters can send reminders to team members who haven't submitted their daily update: - -1. Navigate to the Daily Scrum page -2. View the list of team members who haven't submitted -3. Click "Send Reminder" to notify them -4. Reminders are sent via in-app notifications - -> **Note**: Reminders are manual and sent by the Scrum Master. Automatic scheduled reminders are not currently implemented. - ---- - **Related Topics**: - [Sprint Board](./sprint-board.md) - Track work progress -- [Sprint Planning](./sprint-planning.md) - Set sprint goal +- [Sprint Planning](./sprint-planning.md) - Set the Sprint Goal - [Retrospectives](./retrospectives.md) - Improve the process diff --git a/docs/user-guide/core-features/sprint-board.md b/docs/user-guide/core-features/sprint-board.md index a7c059a6..38d3487d 100644 --- a/docs/user-guide/core-features/sprint-board.md +++ b/docs/user-guide/core-features/sprint-board.md @@ -43,26 +43,27 @@ The Sprint Board provides: The board displays columns representing workflow stages: ``` -┌──────────┬──────────────┬──────────┐ -│ TO DO │ IN PROGRESS │ DONE │ -├──────────┼──────────────┼──────────┤ -│ │ │ │ -│ [Item] │ [Item] │ [Item] │ -│ │ │ │ -│ [Task] │ [Task] │ [Task] │ -│ │ │ │ -└──────────┴──────────────┴──────────┘ +┌──────────┬──────────────┬───────────┬──────────┐ +│ TO DO │ IN PROGRESS │ REVIEW │ DONE │ +├──────────┼──────────────┼───────────┼──────────┤ +│ │ │ │ │ +│ [Item] │ [Item] │ [Item] │ [Item] │ +│ │ │ │ │ +│ [Task] │ [Task] │ [Task] │ [Task] │ +│ │ │ │ │ +└──────────┴──────────────┴───────────┴──────────┘ ``` ### Column Definitions -| Column | Meaning | Who Moves Items Here | -| --------------- | ---------------------- | ------------------------ | -| **To Do** | Ready to start | Default for new tasks | -| **In Progress** | Being worked on | Developer starts work | -| **Done** | Completed and verified | Developer completes work | +| Column | Meaning | Who Moves Items Here | +| --------------- | ---------------------- | ---------------------------- | +| **To Do** | Ready to start | Default for new tasks | +| **In Progress** | Being worked on | Developer starts work | +| **Review** | Awaiting review | Developer submits for review | +| **Done** | Completed and verified | Reviewer confirms complete | -> **Note**: The Sprint Board uses a simple three-column workflow for tasks. For backlog items, the full ItemStatus workflow is: New → Refined → Ready → In Progress → Done. +> **Note**: The Sprint Board uses a four-column workflow for tasks: To Do → In Progress → Review → Done. For backlog items, the full ItemStatus workflow is: New → Refined → Ready → In Progress → Done. ### Swimlanes (Optional) @@ -99,15 +100,6 @@ From the card or detail view: | **Change Status** | Drag card or use status dropdown | | **Delete** | From detail modal (with confirmation) | -### Status Indicators - -Cards show visual indicators: - -- **🔴 Overdue**: Past due date -- **🟡 Blocked**: Has impediment -- **🟢 On Track**: Normal status -- **✓ DoD Complete**: All checklist items done - --- ## Task Management @@ -155,8 +147,21 @@ For backlog item "User can reset password": 1. Move task to "In Progress" when starting 2. Do the work -3. Verify Definition of Done checklist (if configured) -4. Move to "Done" when work is complete +3. Move task to "Review" when ready for a reviewer +4. Reviewer checks the work against the Definition of Done checklist (if configured) +5. Move to "Done" when the review is complete and accepted + +### Marking a PBI as Done + +A backlog item (PBI) is considered done only when **all of its child tasks are Done**. Once every task under the PBI is in the Done column, the board flags the PBI as "ready to done" and you can promote it to **Done** from the board: + +1. **Open the PBI preview** by clicking the PBI card on the board. +2. **Review the tasks** — the preview shows a "ready to done" banner once all child tasks are complete. +3. **Click "Mark as Done"** — the system re-checks that every child task is Done. If any are incomplete, it lists them and blocks the transition. +4. **Complete the Definition of Done checklist** — the active DoD criteria load inline in the preview. Verify each one. +5. **Confirm** — this records the DoD verification and updates the PBI status to **Done**. The board refreshes automatically. + +> **Note**: Marking a PBI Done is restricted to developers with permission, and the DoD checklist must be fully verified before the transition is allowed. --- diff --git a/docs/user-guide/core-features/sprint-planning.md b/docs/user-guide/core-features/sprint-planning.md index 4b922515..6497a141 100644 --- a/docs/user-guide/core-features/sprint-planning.md +++ b/docs/user-guide/core-features/sprint-planning.md @@ -10,6 +10,8 @@ Sprint Planning is the ceremony where the Scrum Team defines the Sprint Goal and - [Capacity Planning](#capacity-planning) - [Selecting Backlog Items](#selecting-backlog-items) - [Defining the Sprint Goal](#defining-the-sprint-goal) +- [Saving the Sprint Backlog](#saving-the-sprint-backlog) +- [Resuming Planning](#resuming-planning) - [Starting the Sprint](#starting-the-sprint) - [Best Practices](#best-practices) @@ -284,6 +286,58 @@ A Sprint Goal should be: --- +## Saving the Sprint Backlog + +Your planning work is preserved in two complementary ways, so you never lose progress and can always resume where you left off. + +### The "Save Sprint Backlog" Button + +As a **Developer**, you can explicitly save the current plan at any time: + +1. Select the items you want in the sprint backlog and break them down into tasks. +2. Click **"Save Sprint Backlog"** in the sprint actions toolbar. +3. The selected items and their task decomposition are saved for the sprint, and a confirmation toast confirms the save. + +Behavior notes: + +- While the save is running, the button shows **"Saving Sprint Backlog..."** and is disabled. +- The button is disabled when no items are selected or when the sprint is already **Active** or **Completed** — in those states the Sprint Backlog is locked and cannot be modified. +- Saving is **idempotent**: each save replaces the previously saved backlog for that sprint with the current selection. +- A saved Sprint Backlog is **required before you can start the sprint** — the "Start Sprint" button stays disabled until the backlog has been saved (see [Starting the Sprint](#starting-the-sprint)). + +### Automatic Draft Saving + +- **Incremental auto-save**: As you select backlog items, draft task assignments, or update the Sprint Goal, the planning state is saved to the server automatically (debounced). You do **not** need to remember to save before leaving. +- **Unload flush**: If you close the page or navigate away mid-planning, any pending changes are flushed so nothing is lost. +- The explicit **"Save Sprint Backlog"** button captures a reviewable snapshot of the plan; the auto-save keeps that snapshot up to date continuously in the background. + +--- + +## Resuming Planning + +Sprint Planning is a collaborative ceremony. To honor the Scrum Guide's principle that the **Sprint Backlog** is a persistent, highly-visible artifact owned by the Developers, Scrumooth lets you resume an interrupted session seamlessly. + +### Re-entering the Planning Page + +When you (or another Developer) open Sprint Planning for the same sprint again: + +1. The saved planning draft is loaded automatically. +2. The **selected items, their tasks (including assignees), and the working Sprint Goal** are restored and pre-filled in the interface. +3. A notice confirms the draft was resumed, so you can continue exactly where you left off — even after closing the page, switching sprints, or signing out. +4. If no saved draft exists for the sprint yet, the planning page starts from a clean slate. + +### Shared and Collaborative + +Because the draft is stored server-side, it is **shared across the team**. Any Developer who opens the sprint sees the same plan. This reflects the Developers' collective selection and decomposition of the work, as Scrum expects. + +### Commit Remains Explicit + +Auto-save and the **"Save Sprint Backlog"** button persist the **draft** only. Starting the Sprint remains the single, explicit commit action that transitions the sprint to **Active** and turns the draft into the committed Sprint Backlog used on the Sprint Board. + +> **Note**: Saving the Sprint Backlog is available to Developers. Other team members can view the plan, but only Developers can modify the sprint backlog draft. + +--- + ## Starting the Sprint ### Pre-Start Checklist @@ -291,6 +345,7 @@ A Sprint Goal should be: Before starting the sprint, verify: - [ ] Sprint Goal is defined and agreed +- [ ] **The Sprint Backlog has been saved** (required — "Start Sprint" stays disabled until you save) - [ ] Selected items fit within capacity - [ ] All items have acceptance criteria - [ ] Team commits to the sprint backlog @@ -305,6 +360,7 @@ Before starting the sprint, verify: - Sprint Goal is clear 2. **Click "Start Sprint"** + - "Start Sprint" is only enabled after the Sprint Backlog has been saved. If you change the plan after saving, click **"Save Sprint Backlog"** again before starting. - The Start Sprint modal opens - Review sprint details (name, dates, goal) - Review selected backlog items and tasks diff --git a/k6/scripts/lib/config.js b/k6/scripts/lib/config.js index 9e59f225..2c2cb74a 100644 --- a/k6/scripts/lib/config.js +++ b/k6/scripts/lib/config.js @@ -91,7 +91,7 @@ export const endpointThresholds = { insights: { p95: 500, p99: 900 }, burndown: { p95: 300, p99: 500 }, }, - dailyUpdates: { + dailyScrums: { create: { p95: 150, p99: 250 }, list: { p95: 200, p99: 350 }, }, diff --git a/k6/scripts/lib/helpers.js b/k6/scripts/lib/helpers.js index a34eb0e8..88b5c03e 100644 --- a/k6/scripts/lib/helpers.js +++ b/k6/scripts/lib/helpers.js @@ -308,32 +308,28 @@ export function generateTaskData(sprintId, pbiId, vu, iteration) { }; } -export function generateDailyUpdateData(sprintId, vu) { - const tasks = [ - 'Completed code review for feature X', - 'Fixed bug in authentication module', - 'Implemented unit tests for service layer', - 'Updated documentation for API endpoints', - 'Refactored database queries for performance', - 'Added validation to form inputs', - 'Integrated third-party API', - 'Resolved merge conflicts in PR', +export function generateDailyScrumData(sprintId, vu) { + const progressNotes = [ + 'Team is on track toward the Sprint Goal; most items are in review.', + 'Progressing steadily; two backlog items were completed this morning.', + 'Slightly behind on the goal due to a blocking dependency; unblocking now.', + 'Working through the Sprint Backlog item by item; goal is achievable.', ]; - const impediments = [ - null, - null, - null, - 'Waiting for API credentials from DevOps', - 'Need clarification on requirements from PO', - 'Blocked by dependency on Team Alpha', + const plans = [ + 'Complete the remaining review items and update the Sprint Backlog.', + 'Pair on the blocked integration and plan the next Sprint Backlog item.', + 'Finish the current task and hold a short refinement for tomorrow.', + 'Resolve the dependency and continue with the next priority item.', ]; + const focusModes = ['goal', 'impediment', 'sprint_backlog_walk', 'pair_up_plan']; + return { sprintId, - yesterdayWork: randomItem(tasks), - todayWork: randomItem(tasks), - impediment: randomItem(impediments), + progressNotes: randomItem(progressNotes), + planForNextDay: randomItem(plans), + focusMode: randomItem(focusModes), }; } diff --git a/k6/scripts/lib/metrics.js b/k6/scripts/lib/metrics.js index 09f5548f..e105e12c 100644 --- a/k6/scripts/lib/metrics.js +++ b/k6/scripts/lib/metrics.js @@ -5,7 +5,7 @@ export const sprintResponseTime = new Trend('sprint_response_time', true); export const backlogResponseTime = new Trend('backlog_response_time', true); export const taskResponseTime = new Trend('task_response_time', true); export const reportResponseTime = new Trend('report_response_time', true); -export const dailyUpdateResponseTime = new Trend('daily_update_response_time', true); +export const dailyScrumResponseTime = new Trend('daily_scrum_response_time', true); export const authErrorRate = new Rate('auth_errors'); export const apiErrorRate = new Rate('api_errors'); @@ -16,7 +16,7 @@ export const logoutCount = new Counter('logouts'); export const taskUpdateCount = new Counter('task_updates'); export const pbiCreateCount = new Counter('pbi_creates'); export const sprintStartCount = new Counter('sprint_starts'); -export const dailyUpdateCount = new Counter('daily_updates'); +export const dailyScrumCount = new Counter('daily_scrums'); export const activeUsers = new Gauge('active_users'); export const dbConnections = new Gauge('db_connections'); @@ -38,8 +38,8 @@ export function recordResponseTime(category, duration) { case 'report': reportResponseTime.add(duration); break; - case 'dailyUpdate': - dailyUpdateResponseTime.add(duration); + case 'dailyScrum': + dailyScrumResponseTime.add(duration); break; default: break; @@ -81,8 +81,8 @@ export function recordOperation(operation) { case 'sprintStart': sprintStartCount.add(1); break; - case 'dailyUpdate': - dailyUpdateCount.add(1); + case 'dailyScrum': + dailyScrumCount.add(1); break; default: break; diff --git a/k6/scripts/scenarios/daily-scrum-rush.js b/k6/scripts/scenarios/daily-scrum-rush.js index c4a5bb1b..ffabdb8c 100644 --- a/k6/scripts/scenarios/daily-scrum-rush.js +++ b/k6/scripts/scenarios/daily-scrum-rush.js @@ -7,7 +7,7 @@ import { logout, randomFloat, generateTeamUserId, - generateDailyUpdateData, + generateDailyScrumData, getAuthHeaders, getUserTeams, getActiveSprint, @@ -17,7 +17,7 @@ import { recordResponseTime, recordError, recordOperation } from '../lib/metrics const errorRate = new Rate('errors'); const responseTime = new Trend('response_time'); const requestCount = new Counter('requests'); -const dailyUpdatesCreated = new Counter('daily_updates_created'); +const dailyScrumsCreated = new Counter('daily_scrums_created'); export const options = { scenarios: { @@ -74,44 +74,59 @@ export default function dailyScrumRush() { } const sprintId = sprint.id; - const updateData = generateDailyUpdateData(sprintId, __VU); + const scrumData = generateDailyScrumData(sprintId, __VU); - const updateRes = http.post( - `${config.baseUrl}/api/v1/daily-updates`, - JSON.stringify(updateData), - { headers, jar, tags: { operation: 'createDailyUpdate' } } + const createRes = http.post( + `${config.baseUrl}/api/v1/daily-scrums/${sprintId}`, + JSON.stringify(scrumData), + { headers, jar, tags: { operation: 'createDailyScrum' } } ); - check(updateRes, { - 'daily update submitted': (r) => r.status === 201 || r.status === 200, + check(createRes, { + 'daily scrum created': (r) => r.status === 201 || r.status === 200, 'response time acceptable': (r) => r.timings.duration < 300, }); - recordResponseTime('dailyUpdate', updateRes.timings.duration); - responseTime.add(updateRes.timings.duration); + recordResponseTime('dailyScrum', createRes.timings.duration); + responseTime.add(createRes.timings.duration); requestCount.add(1); - if (updateRes.status === 201 || updateRes.status === 200) { - dailyUpdatesCreated.add(1); - recordOperation('dailyUpdate'); + let scrumId = null; + if (createRes.status === 201 || createRes.status === 200) { + dailyScrumsCreated.add(1); + recordOperation('dailyScrum'); + const body = createRes.json(); + scrumId = body?.data?.id || null; } - recordError('api', updateRes.status >= 400); + recordError('api', createRes.status >= 400); sleep(randomFloat(1, 3)); - const listRes = http.get(`${config.baseUrl}/api/v1/daily-updates?sprintId=${sprintId}`, { + const listRes = http.get(`${config.baseUrl}/api/v1/daily-scrums/${sprintId}`, { headers, jar, - tags: { operation: 'listDailyUpdates' }, + tags: { operation: 'listDailyScrums' }, }); check(listRes, { - 'daily updates listed': (r) => r.status === 200, + 'daily scrums listed': (r) => r.status === 200, }); requestCount.add(1); + if (scrumId) { + const participateRes = http.post( + `${config.baseUrl}/api/v1/daily-scrums/record/${scrumId}/participate`, + null, + { headers, jar, tags: { operation: 'participateDailyScrum' } } + ); + check(participateRes, { + 'participation recorded': (r) => r.status === 201 || r.status === 200, + }); + requestCount.add(1); + } + sleep(randomFloat(2, 5)); logout(token, csrfToken, csrfCookie); @@ -129,7 +144,7 @@ Daily Scrum Rush Test Summary ======================================== Total Requests: ${data.metrics.http_reqs?.values?.count || 0} Request Rate: ${(data.metrics.http_reqs?.values?.rate || 0).toFixed(2)} req/s -Daily Updates Created: ${data.metrics.daily_updates_created?.values?.count || 0} +Daily Scrums Created: ${data.metrics.daily_scrums_created?.values?.count || 0} Response Times: - P50: ${(data.metrics.http_req_duration?.values?.['p(50)'] || 0).toFixed(2)}ms diff --git a/k6/scripts/scenarios/workday-endurance.js b/k6/scripts/scenarios/workday-endurance.js index 515966ea..e2f29d09 100644 --- a/k6/scripts/scenarios/workday-endurance.js +++ b/k6/scripts/scenarios/workday-endurance.js @@ -8,7 +8,7 @@ import { randomFloat, randomItem, generateTeamUserId, - generateDailyUpdateData, + generateDailyScrumData, getAuthHeaders, getUserTeams, getActiveSprint, @@ -101,17 +101,21 @@ function executeOperation(operation, token, csrfToken, jar, teamId, sprintId) { requestCount.add(1); break; } - case 'submitDailyUpdate': { - const updateData = generateDailyUpdateData(sprintId, __VU); - const res = http.post(`${config.baseUrl}/api/v1/daily-updates`, JSON.stringify(updateData), { - headers, - jar, - tags: { operation: 'submitDailyUpdate' }, - }); - check(res, { 'daily update submitted': (r) => r.status === 201 || r.status === 200 }); - recordResponseTime('dailyUpdate', res.timings.duration); + case 'submitDailyScrum': { + const scrumData = generateDailyScrumData(sprintId, __VU); + const res = http.post( + `${config.baseUrl}/api/v1/daily-scrums/${sprintId}`, + JSON.stringify(scrumData), + { + headers, + jar, + tags: { operation: 'submitDailyScrum' }, + } + ); + check(res, { 'daily scrum submitted': (r) => r.status === 201 || r.status === 200 }); + recordResponseTime('dailyScrum', res.timings.duration); requestCount.add(1); - recordOperation('dailyUpdate'); + recordOperation('dailyScrum'); break; } default: @@ -159,7 +163,7 @@ export default function workdayEnduranceTest() { { name: 'viewBacklog', weight: 25 }, { name: 'updateTask', weight: 20 }, { name: 'viewReports', weight: 15 }, - { name: 'submitDailyUpdate', weight: 10 }, + { name: 'submitDailyScrum', weight: 10 }, ]; const totalWeight = operations.reduce((sum, op) => sum + op.weight, 0); diff --git a/packages/backend/.env.example b/packages/backend/.env.example index 418dc017..ff49f195 100644 --- a/packages/backend/.env.example +++ b/packages/backend/.env.example @@ -29,7 +29,8 @@ # 13. Registration Configuration # 14. Database Transaction Configuration # 15. Backlog Configuration -# 16. Email Configuration +# 16. Team Configuration +# 17. Email Configuration # =========================================== # =========================================== @@ -282,7 +283,15 @@ REGISTRATION_ALLOWED_EMAIL_DOMAINS=example.com,example.eu BACKLOG_MAX_ITEMS_PER_GOAL=200 # =========================================== -# 16. Email Configuration +# 16. Team Configuration +# =========================================== +# Maximum number of members allowed per Scrum Team +# Scrum Guide recommends "typically 10 or fewer" people per team +# Type: integer | Default: 10 | Optional +TEAM_MAX_SIZE=10 + +# =========================================== +# 17. Email Configuration # =========================================== # Email provider # Type: string | Default: smtp | Required for production diff --git a/packages/backend/.env.production.example b/packages/backend/.env.production.example index 73f1bd9c..f87f4453 100644 --- a/packages/backend/.env.production.example +++ b/packages/backend/.env.production.example @@ -31,8 +31,9 @@ # 13. Registration Configuration # 14. Database Transaction Configuration # 15. Backlog Configuration -# 16. Email Configuration -# 17. Deployment Notes +# 16. Team Configuration +# 17. Email Configuration +# 18. Deployment Notes # =========================================== # =========================================== @@ -295,7 +296,15 @@ HEALTH_CHECK_DATABASE_TIMEOUT=5000 BACKLOG_MAX_ITEMS_PER_GOAL=200 # =========================================== -# 16. Email Configuration +# 16. Team Configuration +# =========================================== +# Maximum number of members allowed per Scrum Team +# Scrum Guide recommends "typically 10 or fewer" people per team +# Type: integer | Default: 10 | Optional +TEAM_MAX_SIZE=10 + +# =========================================== +# 17. Email Configuration # =========================================== # Email provider # Type: string | REQUIRED diff --git a/packages/backend/prisma/migrations/00000000000000_init/migration.sql b/packages/backend/prisma/migrations/00000000000000_init/migration.sql index c10f80eb..8d75e61e 100644 --- a/packages/backend/prisma/migrations/00000000000000_init/migration.sql +++ b/packages/backend/prisma/migrations/00000000000000_init/migration.sql @@ -1,61 +1,40 @@ +-- ============================================ +-- INIT MIGRATION +-- Consolidated initial schema for Scrumooth. +-- ============================================ + -- CreateSchema CREATE SCHEMA IF NOT EXISTS "public"; --- CreateEnum -CREATE TYPE "UserRole" AS ENUM ('PRODUCT_OWNER', 'SCRUM_MASTER', 'DEVELOPER'); - --- CreateEnum +-- ============================================ +-- ENUMS +-- ============================================ +CREATE TYPE "UserRole" AS ENUM ('PRODUCT_OWNER', 'SCRUM_MASTER', 'DEVELOPERS'); CREATE TYPE "ItemStatus" AS ENUM ('NEW', 'REFINED', 'READY', 'IN_PROGRESS', 'DONE'); - --- CreateEnum -CREATE TYPE "TaskStatus" AS ENUM ('TODO', 'IN_PROGRESS', 'DONE'); - --- CreateEnum -CREATE TYPE "SprintStatus" AS ENUM ('PLANNED', 'ACTIVE', 'COMPLETED', 'CANCELLED'); - --- CreateEnum +CREATE TYPE "TaskStatus" AS ENUM ('TODO', 'IN_PROGRESS', 'REVIEW', 'DONE'); +CREATE TYPE "SprintStatus" AS ENUM ('DRAFT', 'PLANNED', 'ACTIVE', 'COMPLETED', 'CANCELLED'); CREATE TYPE "ImpedimentStatus" AS ENUM ('OPEN', 'IN_PROGRESS', 'RESOLVED', 'CLOSED'); - --- CreateEnum CREATE TYPE "ProductGoalStatus" AS ENUM ('NEW', 'ACTIVE', 'COMPLETED', 'ABANDONED'); - --- CreateEnum CREATE TYPE "MoSCoWPriority" AS ENUM ('MUST_HAVE', 'SHOULD_HAVE', 'COULD_HAVE', 'WONT_HAVE'); - --- CreateEnum CREATE TYPE "RetrospectiveCategory" AS ENUM ('WENT_WELL', 'DIDNT_GO_WELL', 'IMPROVEMENT'); - --- CreateEnum CREATE TYPE "IncrementStatus" AS ENUM ('DRAFT', 'VERIFIED', 'DELIVERED', 'ARCHIVED'); - --- CreateEnum CREATE TYPE "DeliveryMethod" AS ENUM ('SPRINT_REVIEW', 'EARLY_RELEASE'); - --- CreateEnum +CREATE TYPE "IntegrationTestResult" AS ENUM ('PENDING', 'PASSED', 'FAILED'); +CREATE TYPE "ScrumValue" AS ENUM ('COMMITMENT', 'FOCUS', 'OPENNESS', 'RESPECT', 'COURAGE'); +CREATE TYPE "HealthCheckStatus" AS ENUM ('OPEN', 'CLOSED'); CREATE TYPE "FeedbackCategory" AS ENUM ('POSITIVE', 'NEGATIVE', 'SUGGESTION', 'QUESTION'); - --- CreateEnum CREATE TYPE "ActionItemStatus" AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED'); - --- CreateEnum CREATE TYPE "RetrospectiveStatus" AS ENUM ('DRAFT', 'IN_PROGRESS', 'COMPLETED'); - --- CreateEnum +CREATE TYPE "TimeboxStatus" AS ENUM ('IDLE', 'RUNNING', 'PAUSED'); CREATE TYPE "SprintDuration" AS ENUM ('ONE_WEEK', 'TWO_WEEKS', 'THREE_WEEKS', 'FOUR_WEEKS'); - --- CreateEnum CREATE TYPE "ScheduledDeletionStatus" AS ENUM ('PENDING', 'CANCELLED', 'EXECUTED', 'EXPIRED'); - --- CreateEnum -CREATE TYPE "NotificationType" AS ENUM ('TEAM_INVITATION', 'TEAM_REMOVAL', 'TASK_ASSIGNMENT', 'IMPEDIMENT_ASSIGNMENT', 'DAILY_UPDATE_REMINDER', 'TEAM_CREATED', 'TEAM_UPDATED', 'TEAM_DELETED', 'DIRECT_MESSAGE', 'ACCOUNT_DELETION_SCHEDULED', 'ACCOUNT_DELETION_CANCELLED'); - --- CreateEnum +CREATE TYPE "NotificationType" AS ENUM ('TEAM_INVITATION', 'TEAM_REMOVAL', 'TASK_ASSIGNMENT', 'IMPEDIMENT_ASSIGNMENT', 'DAILY_SCRUM_SIGNAL', 'TEAM_CREATED', 'TEAM_UPDATED', 'TEAM_DELETED', 'DIRECT_MESSAGE', 'ACCOUNT_DELETION_SCHEDULED', 'ACCOUNT_DELETION_CANCELLED'); CREATE TYPE "EmailType" AS ENUM ('PASSWORD_RESET', 'EMAIL_VERIFICATION', 'WELCOME', 'PASSWORD_CHANGE', 'ACCOUNT_DELETION', 'TEAM_INVITATION', 'NOTIFICATION'); - --- CreateEnum CREATE TYPE "EmailStatus" AS ENUM ('PENDING', 'SENT', 'DELIVERED', 'FAILED', 'BOUNCED', 'OPENED', 'CLICKED'); --- CreateTable +-- ============================================ +-- TABLES +-- ============================================ CREATE TABLE "users" ( "id" UUID NOT NULL, "email" TEXT NOT NULL, @@ -63,6 +42,7 @@ CREATE TABLE "users" ( "firstName" TEXT NOT NULL, "lastName" TEXT NOT NULL, "avatarUrl" TEXT, + "locale" VARCHAR(10) NOT NULL DEFAULT 'en', "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdBy" UUID, "updatedAt" TIMESTAMPTZ(3) NOT NULL, @@ -74,7 +54,6 @@ CREATE TABLE "users" ( CONSTRAINT "users_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "teams" ( "id" UUID NOT NULL, "name" TEXT NOT NULL, @@ -87,7 +66,6 @@ CREATE TABLE "teams" ( CONSTRAINT "teams_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "team_members" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -102,7 +80,6 @@ CREATE TABLE "team_members" ( CONSTRAINT "team_members_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "product_goals" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -120,7 +97,6 @@ CREATE TABLE "product_goals" ( CONSTRAINT "product_goals_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "product_backlog_items" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -142,7 +118,6 @@ CREATE TABLE "product_backlog_items" ( CONSTRAINT "product_backlog_items_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "sprints" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -153,6 +128,7 @@ CREATE TABLE "sprints" ( "sprintGoal" TEXT, "status" "SprintStatus" NOT NULL DEFAULT 'PLANNED', "cancellationReason" TEXT, + "smNotes" TEXT, "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdBy" UUID, "updatedAt" TIMESTAMPTZ(3) NOT NULL, @@ -161,7 +137,6 @@ CREATE TABLE "sprints" ( CONSTRAINT "sprints_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "sprint_backlog_items" ( "id" UUID NOT NULL, "sprintId" UUID NOT NULL, @@ -174,7 +149,6 @@ CREATE TABLE "sprint_backlog_items" ( CONSTRAINT "sprint_backlog_items_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "sprint_backlog_changes" ( "id" UUID NOT NULL, "sprintId" UUID NOT NULL, @@ -192,7 +166,6 @@ CREATE TABLE "sprint_backlog_changes" ( CONSTRAINT "sprint_backlog_changes_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "tasks" ( "id" UUID NOT NULL, "sprintId" UUID NOT NULL, @@ -211,24 +184,45 @@ CREATE TABLE "tasks" ( CONSTRAINT "tasks_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "daily_updates" ( +CREATE TABLE "daily_scrums" ( "id" UUID NOT NULL, "sprintId" UUID NOT NULL, - "userId" UUID NOT NULL, - "updateDate" DATE NOT NULL, - "yesterdayWork" TEXT, - "todayWork" TEXT, - "impediment" TEXT, + "scrumDate" DATE NOT NULL, + "progressNotes" TEXT, + "adaptationsNotes" TEXT, + "planForNextDay" TEXT, + "focusMode" TEXT, "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdBy" UUID, "updatedAt" TIMESTAMPTZ(3) NOT NULL, "updatedBy" UUID, - CONSTRAINT "daily_updates_pkey" PRIMARY KEY ("id") + CONSTRAINT "daily_scrums_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "daily_scrum_backlog_items" ( + "id" UUID NOT NULL, + "dailyScrumId" UUID NOT NULL, + "sprintBacklogItemId" UUID NOT NULL, + "action" TEXT NOT NULL, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" UUID, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "updatedBy" UUID, + + CONSTRAINT "daily_scrum_backlog_items_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "daily_scrum_participants" ( + "id" UUID NOT NULL, + "dailyScrumId" UUID NOT NULL, + "userId" UUID NOT NULL, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" UUID, + + CONSTRAINT "daily_scrum_participants_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "burndown_data" ( "id" UUID NOT NULL, "sprintId" UUID NOT NULL, @@ -243,7 +237,6 @@ CREATE TABLE "burndown_data" ( CONSTRAINT "burndown_data_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "impediments" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -255,7 +248,6 @@ CREATE TABLE "impediments" ( "status" "ImpedimentStatus" NOT NULL DEFAULT 'OPEN', "resolution" TEXT, "resolvedAt" TIMESTAMPTZ(3), - "dailyUpdateId" UUID, "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdBy" UUID, "updatedAt" TIMESTAMPTZ(3) NOT NULL, @@ -264,7 +256,6 @@ CREATE TABLE "impediments" ( CONSTRAINT "impediments_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "definition_of_done" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -277,7 +268,6 @@ CREATE TABLE "definition_of_done" ( CONSTRAINT "definition_of_done_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "dod_items" ( "id" UUID NOT NULL, "dodId" UUID NOT NULL, @@ -293,7 +283,6 @@ CREATE TABLE "dod_items" ( CONSTRAINT "dod_items_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "dod_checklist_verifications" ( "id" UUID NOT NULL, "pbiId" UUID NOT NULL, @@ -310,7 +299,6 @@ CREATE TABLE "dod_checklist_verifications" ( CONSTRAINT "dod_checklist_verifications_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "definition_of_ready" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -323,7 +311,6 @@ CREATE TABLE "definition_of_ready" ( CONSTRAINT "definition_of_ready_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "dor_items" ( "id" UUID NOT NULL, "dorId" UUID NOT NULL, @@ -339,7 +326,6 @@ CREATE TABLE "dor_items" ( CONSTRAINT "dor_items_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "dor_checklist_verifications" ( "id" UUID NOT NULL, "pbiId" UUID NOT NULL, @@ -356,7 +342,6 @@ CREATE TABLE "dor_checklist_verifications" ( CONSTRAINT "dor_checklist_verifications_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "increments" ( "id" UUID NOT NULL, "sprintId" UUID NOT NULL, @@ -365,6 +350,7 @@ CREATE TABLE "increments" ( "description" TEXT, "totalStoryPoints" INTEGER NOT NULL DEFAULT 0, "status" "IncrementStatus" NOT NULL DEFAULT 'DRAFT', + "integrationVerified" BOOLEAN NOT NULL DEFAULT false, "deliveredAt" TIMESTAMPTZ(3), "deliveryMethod" "DeliveryMethod", "notes" TEXT, @@ -376,7 +362,6 @@ CREATE TABLE "increments" ( CONSTRAINT "increments_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "increment_pbis" ( "id" UUID NOT NULL, "incrementId" UUID NOT NULL, @@ -390,7 +375,6 @@ CREATE TABLE "increment_pbis" ( CONSTRAINT "increment_pbis_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "sprint_reviews" ( "id" UUID NOT NULL, "sprintId" UUID NOT NULL, @@ -399,6 +383,7 @@ CREATE TABLE "sprint_reviews" ( "reviewDate" TIMESTAMPTZ(3) NOT NULL, "summary" TEXT, "status" TEXT NOT NULL DEFAULT 'in_progress', + "smNotes" TEXT, "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdBy" UUID, "updatedAt" TIMESTAMPTZ(3) NOT NULL, @@ -407,7 +392,6 @@ CREATE TABLE "sprint_reviews" ( CONSTRAINT "sprint_reviews_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "review_attendees" ( "id" UUID NOT NULL, "reviewId" UUID NOT NULL, @@ -423,7 +407,6 @@ CREATE TABLE "review_attendees" ( CONSTRAINT "review_attendees_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "stakeholder_feedback" ( "id" UUID NOT NULL, "reviewId" UUID NOT NULL, @@ -434,6 +417,7 @@ CREATE TABLE "stakeholder_feedback" ( "actionRequired" BOOLEAN NOT NULL DEFAULT false, "actionTaken" BOOLEAN NOT NULL DEFAULT false, "ownerId" UUID, + "productGoalAssessment" TEXT, "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdBy" UUID, "updatedAt" TIMESTAMPTZ(3) NOT NULL, @@ -442,7 +426,6 @@ CREATE TABLE "stakeholder_feedback" ( CONSTRAINT "stakeholder_feedback_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "backlog_adjustments" ( "id" UUID NOT NULL, "reviewId" UUID NOT NULL, @@ -460,7 +443,6 @@ CREATE TABLE "backlog_adjustments" ( CONSTRAINT "backlog_adjustments_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "sprint_retrospectives" ( "id" UUID NOT NULL, "sprintId" UUID NOT NULL, @@ -471,6 +453,7 @@ CREATE TABLE "sprint_retrospectives" ( "status" "RetrospectiveStatus" NOT NULL DEFAULT 'DRAFT', "summary" TEXT, "dodEvolutionNotes" TEXT, + "smNotes" TEXT, "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdBy" UUID, "updatedAt" TIMESTAMPTZ(3) NOT NULL, @@ -479,7 +462,6 @@ CREATE TABLE "sprint_retrospectives" ( CONSTRAINT "sprint_retrospectives_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "retro_attendees" ( "id" UUID NOT NULL, "retrospectiveId" UUID NOT NULL, @@ -495,7 +477,6 @@ CREATE TABLE "retro_attendees" ( CONSTRAINT "retro_attendees_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "retrospective_items" ( "id" UUID NOT NULL, "retrospectiveId" UUID NOT NULL, @@ -513,7 +494,6 @@ CREATE TABLE "retrospective_items" ( CONSTRAINT "retrospective_items_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "retro_item_votes" ( "id" UUID NOT NULL, "retrospectiveItemId" UUID NOT NULL, @@ -526,7 +506,6 @@ CREATE TABLE "retro_item_votes" ( CONSTRAINT "retro_item_votes_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "retro_action_items" ( "id" UUID NOT NULL, "retrospectiveId" UUID NOT NULL, @@ -546,7 +525,6 @@ CREATE TABLE "retro_action_items" ( CONSTRAINT "retro_action_items_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "sprint_configurations" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -562,7 +540,6 @@ CREATE TABLE "sprint_configurations" ( CONSTRAINT "sprint_configurations_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "generated_sprints" ( "id" UUID NOT NULL, "teamId" UUID NOT NULL, @@ -582,7 +559,6 @@ CREATE TABLE "generated_sprints" ( CONSTRAINT "generated_sprints_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "workflows" ( "id" UUID NOT NULL, "entityType" TEXT NOT NULL, @@ -597,7 +573,6 @@ CREATE TABLE "workflows" ( CONSTRAINT "workflows_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "workflow_states" ( "id" UUID NOT NULL, "workflowId" UUID NOT NULL, @@ -613,7 +588,6 @@ CREATE TABLE "workflow_states" ( CONSTRAINT "workflow_states_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "workflow_transitions" ( "id" UUID NOT NULL, "workflowId" UUID NOT NULL, @@ -629,7 +603,6 @@ CREATE TABLE "workflow_transitions" ( CONSTRAINT "workflow_transitions_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "status_change_history" ( "id" UUID NOT NULL, "entityType" TEXT NOT NULL, @@ -647,13 +620,14 @@ CREATE TABLE "status_change_history" ( CONSTRAINT "status_change_history_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "notifications" ( "id" UUID NOT NULL, "userId" UUID NOT NULL, "type" "NotificationType" NOT NULL, "title" TEXT NOT NULL, "message" TEXT, + "messageKey" TEXT, + "params" JSONB, "data" JSONB, "isRead" BOOLEAN NOT NULL DEFAULT false, "readAt" TIMESTAMPTZ(3), @@ -665,7 +639,6 @@ CREATE TABLE "notifications" ( CONSTRAINT "notifications_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "system_parameters" ( "id" UUID NOT NULL, "key" TEXT NOT NULL, @@ -679,7 +652,6 @@ CREATE TABLE "system_parameters" ( CONSTRAINT "system_parameters_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "refresh_tokens" ( "id" UUID NOT NULL, "token" TEXT NOT NULL, @@ -698,7 +670,6 @@ CREATE TABLE "refresh_tokens" ( CONSTRAINT "refresh_tokens_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "scheduled_deletions" ( "id" UUID NOT NULL, "user_id" UUID NOT NULL, @@ -717,7 +688,6 @@ CREATE TABLE "scheduled_deletions" ( CONSTRAINT "scheduled_deletions_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "consent_records" ( "id" UUID NOT NULL, "user_id" UUID, @@ -731,7 +701,6 @@ CREATE TABLE "consent_records" ( CONSTRAINT "consent_records_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "email_logs" ( "id" UUID NOT NULL, "userId" UUID, @@ -756,7 +725,6 @@ CREATE TABLE "email_logs" ( CONSTRAINT "email_logs_pkey" PRIMARY KEY ("id") ); --- CreateTable CREATE TABLE "password_reset_tokens" ( "id" UUID NOT NULL, "userId" UUID NOT NULL, @@ -768,654 +736,368 @@ CREATE TABLE "password_reset_tokens" ( CONSTRAINT "password_reset_tokens_pkey" PRIMARY KEY ("id") ); --- CreateIndex -CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); +-- Tables introduced by the scrum lifecycle enhancement +CREATE TABLE "increment_integration_tests" ( + "id" UUID NOT NULL, + "currentIncrementId" UUID NOT NULL, + "priorIncrementId" UUID NOT NULL, + "testResult" "IntegrationTestResult" NOT NULL DEFAULT 'PENDING', + "testedById" UUID NOT NULL, + "testedAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "notes" TEXT, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" UUID, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "updatedBy" UUID, --- CreateIndex -CREATE UNIQUE INDEX "teams_name_key" ON "teams"("name"); + CONSTRAINT "increment_integration_tests_pkey" PRIMARY KEY ("id") +); --- CreateIndex -CREATE INDEX "teams_createdBy_idx" ON "teams"("createdBy"); +CREATE TABLE "product_goal_snapshots" ( + "id" UUID NOT NULL, + "goalId" UUID NOT NULL, + "sprintReviewId" UUID NOT NULL, + "successMetricValues" JSONB, + "completedPbiCount" INTEGER NOT NULL DEFAULT 0, + "completedStoryPoints" INTEGER NOT NULL DEFAULT 0, + "assessment" TEXT, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" UUID, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "updatedBy" UUID, --- CreateIndex -CREATE INDEX "team_members_userId_idx" ON "team_members"("userId"); + CONSTRAINT "product_goal_snapshots_pkey" PRIMARY KEY ("id") +); --- CreateIndex -CREATE UNIQUE INDEX "team_members_teamId_userId_key" ON "team_members"("teamId", "userId"); +CREATE TABLE "team_health_checks" ( + "id" UUID NOT NULL, + "teamId" UUID NOT NULL, + "sprintId" UUID, + "status" "HealthCheckStatus" NOT NULL DEFAULT 'OPEN', + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" UUID, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "updatedBy" UUID, --- CreateIndex -CREATE INDEX "product_goals_teamId_idx" ON "product_goals"("teamId"); + CONSTRAINT "team_health_checks_pkey" PRIMARY KEY ("id") +); --- CreateIndex -CREATE INDEX "product_goals_status_idx" ON "product_goals"("status"); +CREATE TABLE "team_health_check_responses" ( + "id" UUID NOT NULL, + "healthCheckId" UUID NOT NULL, + "userId" UUID NOT NULL, + "scrumValue" "ScrumValue" NOT NULL, + "score" INTEGER NOT NULL, + "anonymous" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" UUID, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "updatedBy" UUID, --- CreateIndex -CREATE INDEX "product_goals_targetDate_idx" ON "product_goals"("targetDate"); + CONSTRAINT "team_health_check_responses_pkey" PRIMARY KEY ("id") +); --- CreateIndex -CREATE INDEX "product_goals_teamId_status_idx" ON "product_goals"("teamId", "status"); +-- Timebox table introduced by the sprint timeboxing feature. +CREATE TABLE "timeboxes" ( + "id" UUID NOT NULL, + "teamId" UUID NOT NULL, + "eventType" TEXT NOT NULL, + "sprintId" UUID, + "date" DATE NOT NULL, + "status" "TimeboxStatus" NOT NULL DEFAULT 'IDLE', + "startedAt" TIMESTAMPTZ(3), + "pausedAt" TIMESTAMPTZ(3), + "accumulatedMs" INTEGER NOT NULL DEFAULT 0, + "version" INTEGER NOT NULL DEFAULT 0, + "concludedAt" TIMESTAMPTZ(3), + "concludedElapsedMs" INTEGER, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" UUID, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "updatedBy" UUID, --- CreateIndex -CREATE INDEX "product_backlog_items_teamId_idx" ON "product_backlog_items"("teamId"); + CONSTRAINT "timeboxes_pkey" PRIMARY KEY ("id") +); --- CreateIndex +-- ============================================ +-- INDEXES +-- ============================================ +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); +CREATE UNIQUE INDEX "teams_name_key" ON "teams"("name"); +CREATE INDEX "teams_createdBy_idx" ON "teams"("createdBy"); +CREATE INDEX "team_members_userId_idx" ON "team_members"("userId"); +CREATE UNIQUE INDEX "team_members_teamId_userId_key" ON "team_members"("teamId", "userId"); +CREATE INDEX "product_goals_teamId_idx" ON "product_goals"("teamId"); +CREATE INDEX "product_goals_status_idx" ON "product_goals"("status"); +CREATE INDEX "product_goals_targetDate_idx" ON "product_goals"("targetDate"); +CREATE INDEX "product_goals_teamId_status_idx" ON "product_goals"("teamId", "status"); +CREATE INDEX "product_backlog_items_teamId_idx" ON "product_backlog_items"("teamId"); CREATE INDEX "product_backlog_items_status_idx" ON "product_backlog_items"("status"); - --- CreateIndex CREATE INDEX "product_backlog_items_goalId_idx" ON "product_backlog_items"("goalId"); - --- CreateIndex CREATE INDEX "product_backlog_items_sprintId_idx" ON "product_backlog_items"("sprintId"); - --- CreateIndex CREATE INDEX "product_backlog_items_priority_idx" ON "product_backlog_items"("priority"); - --- CreateIndex CREATE INDEX "product_backlog_items_teamId_status_idx" ON "product_backlog_items"("teamId", "status"); - --- CreateIndex CREATE INDEX "product_backlog_items_teamId_status_priority_idx" ON "product_backlog_items"("teamId", "status", "priority"); - --- CreateIndex CREATE INDEX "sprints_teamId_idx" ON "sprints"("teamId"); - --- CreateIndex CREATE INDEX "sprints_status_idx" ON "sprints"("status"); - --- CreateIndex CREATE INDEX "sprints_goalId_idx" ON "sprints"("goalId"); - --- CreateIndex CREATE INDEX "sprints_startDate_idx" ON "sprints"("startDate"); - --- CreateIndex CREATE INDEX "sprints_endDate_idx" ON "sprints"("endDate"); - --- CreateIndex CREATE INDEX "sprints_teamId_status_idx" ON "sprints"("teamId", "status"); - --- CreateIndex CREATE INDEX "sprints_teamId_startDate_idx" ON "sprints"("teamId", "startDate"); - --- CreateIndex CREATE INDEX "sprints_teamId_status_startDate_idx" ON "sprints"("teamId", "status", "startDate"); - --- CreateIndex CREATE UNIQUE INDEX "sprint_backlog_items_sprintId_pbiId_key" ON "sprint_backlog_items"("sprintId", "pbiId"); - --- CreateIndex CREATE INDEX "sprint_backlog_changes_sprintId_idx" ON "sprint_backlog_changes"("sprintId"); - --- CreateIndex CREATE INDEX "sprint_backlog_changes_pbiId_idx" ON "sprint_backlog_changes"("pbiId"); - --- CreateIndex CREATE INDEX "sprint_backlog_changes_createdBy_idx" ON "sprint_backlog_changes"("createdBy"); - --- CreateIndex CREATE INDEX "sprint_backlog_changes_createdAt_idx" ON "sprint_backlog_changes"("createdAt"); - --- CreateIndex CREATE INDEX "tasks_sprintId_idx" ON "tasks"("sprintId"); - --- CreateIndex CREATE INDEX "tasks_pbiId_idx" ON "tasks"("pbiId"); - --- CreateIndex CREATE INDEX "tasks_assigneeId_idx" ON "tasks"("assigneeId"); - --- CreateIndex CREATE INDEX "tasks_status_idx" ON "tasks"("status"); - --- CreateIndex CREATE INDEX "tasks_sprintId_status_idx" ON "tasks"("sprintId", "status"); - --- CreateIndex -CREATE INDEX "daily_updates_sprintId_updateDate_idx" ON "daily_updates"("sprintId", "updateDate"); - --- CreateIndex -CREATE INDEX "daily_updates_updateDate_idx" ON "daily_updates"("updateDate"); - --- CreateIndex -CREATE UNIQUE INDEX "daily_updates_sprintId_userId_updateDate_key" ON "daily_updates"("sprintId", "userId", "updateDate"); - --- CreateIndex +CREATE INDEX "daily_scrums_sprintId_scrumDate_idx" ON "daily_scrums"("sprintId", "scrumDate"); +CREATE INDEX "daily_scrums_scrumDate_idx" ON "daily_scrums"("scrumDate"); +CREATE UNIQUE INDEX "daily_scrums_sprintId_scrumDate_key" ON "daily_scrums"("sprintId", "scrumDate"); +CREATE INDEX "daily_scrum_backlog_items_dailyScrumId_idx" ON "daily_scrum_backlog_items"("dailyScrumId"); +CREATE INDEX "daily_scrum_backlog_items_sprintBacklogItemId_idx" ON "daily_scrum_backlog_items"("sprintBacklogItemId"); +CREATE UNIQUE INDEX "daily_scrum_backlog_items_dailyScrumId_sprintBacklogItemId_key" ON "daily_scrum_backlog_items"("dailyScrumId", "sprintBacklogItemId"); +CREATE INDEX "daily_scrum_participants_dailyScrumId_idx" ON "daily_scrum_participants"("dailyScrumId"); +CREATE INDEX "daily_scrum_participants_userId_idx" ON "daily_scrum_participants"("userId"); +CREATE UNIQUE INDEX "daily_scrum_participants_dailyScrumId_userId_key" ON "daily_scrum_participants"("dailyScrumId", "userId"); CREATE UNIQUE INDEX "burndown_data_sprintId_date_key" ON "burndown_data"("sprintId", "date"); - --- CreateIndex -CREATE UNIQUE INDEX "impediments_dailyUpdateId_key" ON "impediments"("dailyUpdateId"); - --- CreateIndex CREATE INDEX "impediments_teamId_idx" ON "impediments"("teamId"); - --- CreateIndex CREATE INDEX "impediments_status_idx" ON "impediments"("status"); - --- CreateIndex CREATE INDEX "impediments_sprintId_idx" ON "impediments"("sprintId"); - --- CreateIndex CREATE INDEX "impediments_reportedById_idx" ON "impediments"("reportedById"); - --- CreateIndex CREATE INDEX "impediments_ownerId_idx" ON "impediments"("ownerId"); - --- CreateIndex CREATE INDEX "impediments_teamId_status_idx" ON "impediments"("teamId", "status"); - --- CreateIndex CREATE UNIQUE INDEX "definition_of_done_teamId_key" ON "definition_of_done"("teamId"); - --- CreateIndex CREATE INDEX "dod_items_dodId_idx" ON "dod_items"("dodId"); - --- CreateIndex CREATE UNIQUE INDEX "dod_items_dodId_order_key" ON "dod_items"("dodId", "order"); - --- CreateIndex CREATE INDEX "dod_checklist_verifications_pbiId_idx" ON "dod_checklist_verifications"("pbiId"); - --- CreateIndex CREATE INDEX "dod_checklist_verifications_dodItemId_idx" ON "dod_checklist_verifications"("dodItemId"); - --- CreateIndex CREATE INDEX "dod_checklist_verifications_verifiedBy_idx" ON "dod_checklist_verifications"("verifiedBy"); - --- CreateIndex CREATE UNIQUE INDEX "dod_checklist_verifications_pbiId_dodItemId_key" ON "dod_checklist_verifications"("pbiId", "dodItemId"); - --- CreateIndex CREATE UNIQUE INDEX "definition_of_ready_teamId_key" ON "definition_of_ready"("teamId"); - --- CreateIndex CREATE INDEX "dor_items_dorId_idx" ON "dor_items"("dorId"); - --- CreateIndex CREATE UNIQUE INDEX "dor_items_dorId_order_key" ON "dor_items"("dorId", "order"); - --- CreateIndex CREATE INDEX "dor_checklist_verifications_pbiId_idx" ON "dor_checklist_verifications"("pbiId"); - --- CreateIndex CREATE INDEX "dor_checklist_verifications_dorItemId_idx" ON "dor_checklist_verifications"("dorItemId"); - --- CreateIndex CREATE INDEX "dor_checklist_verifications_verifiedBy_idx" ON "dor_checklist_verifications"("verifiedBy"); - --- CreateIndex CREATE UNIQUE INDEX "dor_checklist_verifications_pbiId_dorItemId_key" ON "dor_checklist_verifications"("pbiId", "dorItemId"); - --- CreateIndex CREATE INDEX "increments_sprintId_idx" ON "increments"("sprintId"); - --- CreateIndex CREATE INDEX "increments_teamId_idx" ON "increments"("teamId"); - --- CreateIndex CREATE INDEX "increments_status_idx" ON "increments"("status"); - --- CreateIndex CREATE INDEX "increments_deliveredAt_idx" ON "increments"("deliveredAt"); - --- CreateIndex CREATE INDEX "increments_sprintId_status_idx" ON "increments"("sprintId", "status"); - --- CreateIndex CREATE INDEX "increment_pbis_incrementId_idx" ON "increment_pbis"("incrementId"); - --- CreateIndex CREATE INDEX "increment_pbis_pbiId_idx" ON "increment_pbis"("pbiId"); - --- CreateIndex CREATE UNIQUE INDEX "increment_pbis_incrementId_pbiId_key" ON "increment_pbis"("incrementId", "pbiId"); - --- CreateIndex CREATE UNIQUE INDEX "sprint_reviews_sprintId_key" ON "sprint_reviews"("sprintId"); - --- CreateIndex CREATE UNIQUE INDEX "sprint_reviews_incrementId_key" ON "sprint_reviews"("incrementId"); - --- CreateIndex CREATE INDEX "sprint_reviews_teamId_idx" ON "sprint_reviews"("teamId"); - --- CreateIndex CREATE INDEX "sprint_reviews_reviewDate_idx" ON "sprint_reviews"("reviewDate"); - --- CreateIndex CREATE INDEX "review_attendees_reviewId_idx" ON "review_attendees"("reviewId"); - --- CreateIndex CREATE INDEX "stakeholder_feedback_reviewId_idx" ON "stakeholder_feedback"("reviewId"); - --- CreateIndex CREATE INDEX "stakeholder_feedback_ownerId_idx" ON "stakeholder_feedback"("ownerId"); - --- CreateIndex CREATE INDEX "backlog_adjustments_reviewId_idx" ON "backlog_adjustments"("reviewId"); - --- CreateIndex CREATE INDEX "backlog_adjustments_pbiId_idx" ON "backlog_adjustments"("pbiId"); - --- CreateIndex CREATE INDEX "backlog_adjustments_ownerId_idx" ON "backlog_adjustments"("ownerId"); - --- CreateIndex CREATE UNIQUE INDEX "sprint_retrospectives_sprintId_key" ON "sprint_retrospectives"("sprintId"); - --- CreateIndex CREATE INDEX "sprint_retrospectives_teamId_idx" ON "sprint_retrospectives"("teamId"); - --- CreateIndex CREATE INDEX "sprint_retrospectives_facilitatorId_idx" ON "sprint_retrospectives"("facilitatorId"); - --- CreateIndex CREATE INDEX "sprint_retrospectives_status_idx" ON "sprint_retrospectives"("status"); - --- CreateIndex CREATE INDEX "retro_attendees_retrospectiveId_idx" ON "retro_attendees"("retrospectiveId"); - --- CreateIndex CREATE INDEX "retrospective_items_retrospectiveId_idx" ON "retrospective_items"("retrospectiveId"); - --- CreateIndex CREATE INDEX "retrospective_items_authorId_idx" ON "retrospective_items"("authorId"); - --- CreateIndex CREATE INDEX "retrospective_items_retrospectiveId_category_idx" ON "retrospective_items"("retrospectiveId", "category"); - --- CreateIndex CREATE INDEX "retro_item_votes_retrospectiveItemId_idx" ON "retro_item_votes"("retrospectiveItemId"); - --- CreateIndex CREATE INDEX "retro_item_votes_userId_idx" ON "retro_item_votes"("userId"); - --- CreateIndex CREATE UNIQUE INDEX "retro_item_votes_retrospectiveItemId_userId_key" ON "retro_item_votes"("retrospectiveItemId", "userId"); - --- CreateIndex CREATE INDEX "retro_action_items_retrospectiveId_idx" ON "retro_action_items"("retrospectiveId"); - --- CreateIndex CREATE INDEX "retro_action_items_ownerId_idx" ON "retro_action_items"("ownerId"); - --- CreateIndex CREATE INDEX "retro_action_items_relatedSprintId_idx" ON "retro_action_items"("relatedSprintId"); - --- CreateIndex CREATE INDEX "retro_action_items_status_idx" ON "retro_action_items"("status"); - --- CreateIndex CREATE INDEX "retro_action_items_dueDate_idx" ON "retro_action_items"("dueDate"); - --- CreateIndex CREATE INDEX "retro_action_items_completedAt_idx" ON "retro_action_items"("completedAt"); - --- CreateIndex CREATE INDEX "retro_action_items_retrospectiveId_status_idx" ON "retro_action_items"("retrospectiveId", "status"); - --- CreateIndex CREATE UNIQUE INDEX "sprint_configurations_teamId_key" ON "sprint_configurations"("teamId"); - --- CreateIndex CREATE UNIQUE INDEX "generated_sprints_sprintId_key" ON "generated_sprints"("sprintId"); - --- CreateIndex CREATE INDEX "generated_sprints_teamId_idx" ON "generated_sprints"("teamId"); - --- CreateIndex CREATE UNIQUE INDEX "generated_sprints_teamId_year_sprintNumber_key" ON "generated_sprints"("teamId", "year", "sprintNumber"); - --- CreateIndex CREATE UNIQUE INDEX "workflows_entityType_key" ON "workflows"("entityType"); - --- CreateIndex CREATE INDEX "workflow_states_workflowId_idx" ON "workflow_states"("workflowId"); - --- CreateIndex CREATE UNIQUE INDEX "workflow_states_workflowId_name_key" ON "workflow_states"("workflowId", "name"); - --- CreateIndex CREATE UNIQUE INDEX "workflow_states_workflowId_orderIndex_key" ON "workflow_states"("workflowId", "orderIndex"); - --- CreateIndex CREATE INDEX "workflow_transitions_workflowId_idx" ON "workflow_transitions"("workflowId"); - --- CreateIndex CREATE INDEX "workflow_transitions_fromStateId_idx" ON "workflow_transitions"("fromStateId"); - --- CreateIndex CREATE INDEX "workflow_transitions_toStateId_idx" ON "workflow_transitions"("toStateId"); - --- CreateIndex CREATE UNIQUE INDEX "workflow_transitions_workflowId_fromStateId_toStateId_key" ON "workflow_transitions"("workflowId", "fromStateId", "toStateId"); - --- CreateIndex CREATE INDEX "status_change_history_workflowId_idx" ON "status_change_history"("workflowId"); - --- CreateIndex CREATE INDEX "status_change_history_fromStateId_idx" ON "status_change_history"("fromStateId"); - --- CreateIndex CREATE INDEX "status_change_history_toStateId_idx" ON "status_change_history"("toStateId"); - --- CreateIndex CREATE INDEX "status_change_history_changedBy_idx" ON "status_change_history"("changedBy"); - --- CreateIndex CREATE INDEX "status_change_history_transitionId_idx" ON "status_change_history"("transitionId"); - --- CreateIndex CREATE INDEX "status_change_history_entityType_idx" ON "status_change_history"("entityType"); - --- CreateIndex CREATE INDEX "status_change_history_entityId_idx" ON "status_change_history"("entityId"); - --- CreateIndex CREATE INDEX "status_change_history_createdAt_idx" ON "status_change_history"("createdAt"); - --- CreateIndex CREATE INDEX "status_change_history_entityType_entityId_idx" ON "status_change_history"("entityType", "entityId"); - --- CreateIndex CREATE INDEX "notifications_userId_idx" ON "notifications"("userId"); - --- CreateIndex CREATE INDEX "notifications_userId_isRead_idx" ON "notifications"("userId", "isRead"); - --- CreateIndex CREATE INDEX "notifications_createdAt_idx" ON "notifications"("createdAt"); - --- CreateIndex CREATE UNIQUE INDEX "system_parameters_key_key" ON "system_parameters"("key"); - --- CreateIndex CREATE UNIQUE INDEX "refresh_tokens_token_key" ON "refresh_tokens"("token"); - --- CreateIndex CREATE INDEX "refresh_tokens_userId_idx" ON "refresh_tokens"("userId"); - --- CreateIndex CREATE INDEX "refresh_tokens_lastActivityAt_idx" ON "refresh_tokens"("lastActivityAt"); - --- CreateIndex CREATE INDEX "refresh_tokens_expiresAt_idx" ON "refresh_tokens"("expiresAt"); - --- CreateIndex CREATE INDEX "scheduled_deletions_user_id_idx" ON "scheduled_deletions"("user_id"); - --- CreateIndex CREATE INDEX "scheduled_deletions_status_idx" ON "scheduled_deletions"("status"); - --- CreateIndex CREATE INDEX "consent_records_user_id_idx" ON "consent_records"("user_id"); - --- CreateIndex CREATE INDEX "consent_records_consent_type_idx" ON "consent_records"("consent_type"); - --- CreateIndex CREATE INDEX "email_logs_userId_idx" ON "email_logs"("userId"); - --- CreateIndex CREATE INDEX "email_logs_email_idx" ON "email_logs"("email"); - --- CreateIndex CREATE INDEX "email_logs_type_idx" ON "email_logs"("type"); - --- CreateIndex CREATE INDEX "email_logs_status_idx" ON "email_logs"("status"); - --- CreateIndex CREATE INDEX "email_logs_createdAt_idx" ON "email_logs"("createdAt"); - --- CreateIndex CREATE INDEX "email_logs_createdBy_idx" ON "email_logs"("createdBy"); - --- CreateIndex CREATE UNIQUE INDEX "password_reset_tokens_tokenHash_key" ON "password_reset_tokens"("tokenHash"); - --- CreateIndex CREATE INDEX "password_reset_tokens_userId_idx" ON "password_reset_tokens"("userId"); - --- CreateIndex CREATE INDEX "password_reset_tokens_tokenHash_idx" ON "password_reset_tokens"("tokenHash"); - --- CreateIndex CREATE INDEX "password_reset_tokens_expiresAt_idx" ON "password_reset_tokens"("expiresAt"); --- AddForeignKey -ALTER TABLE "teams" ADD CONSTRAINT "teams_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; +-- Indexes for the scrum lifecycle enhancement tables +CREATE UNIQUE INDEX "increment_integration_tests_currentIncrementId_priorIncreme_key" ON "increment_integration_tests"("currentIncrementId", "priorIncrementId"); +CREATE INDEX "increment_integration_tests_currentIncrementId_idx" ON "increment_integration_tests"("currentIncrementId"); +CREATE INDEX "increment_integration_tests_priorIncrementId_idx" ON "increment_integration_tests"("priorIncrementId"); +CREATE INDEX "increment_integration_tests_testedById_idx" ON "increment_integration_tests"("testedById"); +CREATE UNIQUE INDEX "product_goal_snapshots_goalId_sprintReviewId_key" ON "product_goal_snapshots"("goalId", "sprintReviewId"); +CREATE INDEX "product_goal_snapshots_goalId_idx" ON "product_goal_snapshots"("goalId"); +CREATE INDEX "product_goal_snapshots_sprintReviewId_idx" ON "product_goal_snapshots"("sprintReviewId"); +CREATE INDEX "team_health_checks_teamId_idx" ON "team_health_checks"("teamId"); +CREATE INDEX "team_health_checks_sprintId_idx" ON "team_health_checks"("sprintId"); +CREATE INDEX "team_health_checks_status_idx" ON "team_health_checks"("status"); +CREATE INDEX "team_health_checks_teamId_status_createdAt_idx" ON "team_health_checks"("teamId", "status", "createdAt"); +CREATE UNIQUE INDEX "team_health_check_responses_healthCheckId_userId_scrumValue_key" ON "team_health_check_responses"("healthCheckId", "userId", "scrumValue"); +CREATE INDEX "team_health_check_responses_healthCheckId_idx" ON "team_health_check_responses"("healthCheckId"); +CREATE INDEX "team_health_check_responses_userId_idx" ON "team_health_check_responses"("userId"); +CREATE INDEX "team_health_check_responses_healthCheckId_scrumValue_idx" ON "team_health_check_responses"("healthCheckId", "scrumValue"); + +-- Timebox indexes +CREATE INDEX "timeboxes_teamId_idx" ON "timeboxes"("teamId"); +CREATE INDEX "timeboxes_sprintId_idx" ON "timeboxes"("sprintId"); +CREATE INDEX "timeboxes_date_idx" ON "timeboxes"("date"); +CREATE UNIQUE INDEX "timeboxes_teamId_eventType_sprintId_date_key" ON "timeboxes"("teamId", "eventType", "sprintId", "date"); + +-- Partial unique indexes to enforce exactly one Product Owner / Scrum Master per team. +-- (On a fresh database there is no data to deduplicate, so these are created directly.) +CREATE UNIQUE INDEX "team_members_single_product_owner_idx" ON "team_members"("teamId") WHERE "role" = 'PRODUCT_OWNER'; +CREATE UNIQUE INDEX "team_members_single_scrum_master_idx" ON "team_members"("teamId") WHERE "role" = 'SCRUM_MASTER'; + +-- Dashboard-related composite indexes added by the lifecycle enhancement +CREATE INDEX "increments_teamId_status_createdAt_idx" ON "increments"("teamId", "status", "createdAt"); +CREATE INDEX "impediments_teamId_status_createdAt_idx" ON "impediments"("teamId", "status", "createdAt"); +CREATE INDEX "retro_action_items_ownerId_status_dueDate_idx" ON "retro_action_items"("ownerId", "status", "dueDate"); --- AddForeignKey +-- ============================================ +-- FOREIGN KEYS +-- ============================================ +ALTER TABLE "teams" ADD CONSTRAINT "teams_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; ALTER TABLE "team_members" ADD CONSTRAINT "team_members_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "team_members" ADD CONSTRAINT "team_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "product_goals" ADD CONSTRAINT "product_goals_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "product_goals" ADD CONSTRAINT "product_goals_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "product_backlog_items" ADD CONSTRAINT "product_backlog_items_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "product_backlog_items" ADD CONSTRAINT "product_backlog_items_goalId_fkey" FOREIGN KEY ("goalId") REFERENCES "product_goals"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "product_backlog_items" ADD CONSTRAINT "product_backlog_items_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprints" ADD CONSTRAINT "sprints_goalId_fkey" FOREIGN KEY ("goalId") REFERENCES "product_goals"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprints" ADD CONSTRAINT "sprints_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_backlog_items" ADD CONSTRAINT "sprint_backlog_items_pbiId_fkey" FOREIGN KEY ("pbiId") REFERENCES "product_backlog_items"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_backlog_items" ADD CONSTRAINT "sprint_backlog_items_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_backlog_changes" ADD CONSTRAINT "sprint_backlog_changes_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_backlog_changes" ADD CONSTRAINT "sprint_backlog_changes_pbiId_fkey" FOREIGN KEY ("pbiId") REFERENCES "product_backlog_items"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_backlog_changes" ADD CONSTRAINT "sprint_backlog_changes_sprintBacklogItemId_fkey" FOREIGN KEY ("sprintBacklogItemId") REFERENCES "sprint_backlog_items"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_backlog_changes" ADD CONSTRAINT "sprint_backlog_changes_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "tasks" ADD CONSTRAINT "tasks_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "tasks" ADD CONSTRAINT "tasks_pbiId_fkey" FOREIGN KEY ("pbiId") REFERENCES "product_backlog_items"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "tasks" ADD CONSTRAINT "tasks_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "daily_updates" ADD CONSTRAINT "daily_updates_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "daily_updates" ADD CONSTRAINT "daily_updates_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey +ALTER TABLE "daily_scrums" ADD CONSTRAINT "daily_scrums_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "daily_scrum_backlog_items" ADD CONSTRAINT "daily_scrum_backlog_items_dailyScrumId_fkey" FOREIGN KEY ("dailyScrumId") REFERENCES "daily_scrums"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "daily_scrum_backlog_items" ADD CONSTRAINT "daily_scrum_backlog_items_sprintBacklogItemId_fkey" FOREIGN KEY ("sprintBacklogItemId") REFERENCES "sprint_backlog_items"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "daily_scrum_participants" ADD CONSTRAINT "daily_scrum_participants_dailyScrumId_fkey" FOREIGN KEY ("dailyScrumId") REFERENCES "daily_scrums"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "daily_scrum_participants" ADD CONSTRAINT "daily_scrum_participants_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "burndown_data" ADD CONSTRAINT "burndown_data_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "impediments" ADD CONSTRAINT "impediments_dailyUpdateId_fkey" FOREIGN KEY ("dailyUpdateId") REFERENCES "daily_updates"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "impediments" ADD CONSTRAINT "impediments_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "impediments" ADD CONSTRAINT "impediments_reportedById_fkey" FOREIGN KEY ("reportedById") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "impediments" ADD CONSTRAINT "impediments_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "impediments" ADD CONSTRAINT "impediments_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "definition_of_done" ADD CONSTRAINT "definition_of_done_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "dod_items" ADD CONSTRAINT "dod_items_dodId_fkey" FOREIGN KEY ("dodId") REFERENCES "definition_of_done"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "dod_checklist_verifications" ADD CONSTRAINT "dod_checklist_verifications_dodItemId_fkey" FOREIGN KEY ("dodItemId") REFERENCES "dod_items"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "dod_checklist_verifications" ADD CONSTRAINT "dod_checklist_verifications_verifiedBy_fkey" FOREIGN KEY ("verifiedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "definition_of_ready" ADD CONSTRAINT "definition_of_ready_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "dor_items" ADD CONSTRAINT "dor_items_dorId_fkey" FOREIGN KEY ("dorId") REFERENCES "definition_of_ready"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "dor_checklist_verifications" ADD CONSTRAINT "dor_checklist_verifications_dorItemId_fkey" FOREIGN KEY ("dorItemId") REFERENCES "dor_items"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "dor_checklist_verifications" ADD CONSTRAINT "dor_checklist_verifications_verifiedBy_fkey" FOREIGN KEY ("verifiedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "increments" ADD CONSTRAINT "increments_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "increments" ADD CONSTRAINT "increments_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "increments" ADD CONSTRAINT "increments_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "increment_pbis" ADD CONSTRAINT "increment_pbis_incrementId_fkey" FOREIGN KEY ("incrementId") REFERENCES "increments"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "increment_pbis" ADD CONSTRAINT "increment_pbis_pbiId_fkey" FOREIGN KEY ("pbiId") REFERENCES "product_backlog_items"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_reviews" ADD CONSTRAINT "sprint_reviews_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_reviews" ADD CONSTRAINT "sprint_reviews_incrementId_fkey" FOREIGN KEY ("incrementId") REFERENCES "increments"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_reviews" ADD CONSTRAINT "sprint_reviews_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_reviews" ADD CONSTRAINT "sprint_reviews_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "review_attendees" ADD CONSTRAINT "review_attendees_reviewId_fkey" FOREIGN KEY ("reviewId") REFERENCES "sprint_reviews"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "stakeholder_feedback" ADD CONSTRAINT "stakeholder_feedback_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "stakeholder_feedback" ADD CONSTRAINT "stakeholder_feedback_reviewId_fkey" FOREIGN KEY ("reviewId") REFERENCES "sprint_reviews"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "backlog_adjustments" ADD CONSTRAINT "backlog_adjustments_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "backlog_adjustments" ADD CONSTRAINT "backlog_adjustments_reviewId_fkey" FOREIGN KEY ("reviewId") REFERENCES "sprint_reviews"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_retrospectives" ADD CONSTRAINT "sprint_retrospectives_facilitatorId_fkey" FOREIGN KEY ("facilitatorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_retrospectives" ADD CONSTRAINT "sprint_retrospectives_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_retrospectives" ADD CONSTRAINT "sprint_retrospectives_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "retro_attendees" ADD CONSTRAINT "retro_attendees_retrospectiveId_fkey" FOREIGN KEY ("retrospectiveId") REFERENCES "sprint_retrospectives"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "retrospective_items" ADD CONSTRAINT "retrospective_items_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "retrospective_items" ADD CONSTRAINT "retrospective_items_retrospectiveId_fkey" FOREIGN KEY ("retrospectiveId") REFERENCES "sprint_retrospectives"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "retro_item_votes" ADD CONSTRAINT "retro_item_votes_retrospectiveItemId_fkey" FOREIGN KEY ("retrospectiveItemId") REFERENCES "retrospective_items"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "retro_item_votes" ADD CONSTRAINT "retro_item_votes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "retro_action_items" ADD CONSTRAINT "retro_action_items_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "retro_action_items" ADD CONSTRAINT "retro_action_items_retrospectiveId_fkey" FOREIGN KEY ("retrospectiveId") REFERENCES "sprint_retrospectives"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "sprint_configurations" ADD CONSTRAINT "sprint_configurations_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "generated_sprints" ADD CONSTRAINT "generated_sprints_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "generated_sprints" ADD CONSTRAINT "generated_sprints_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "workflows" ADD CONSTRAINT "workflows_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "workflow_states" ADD CONSTRAINT "workflow_states_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflows"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "workflow_transitions" ADD CONSTRAINT "workflow_transitions_fromStateId_fkey" FOREIGN KEY ("fromStateId") REFERENCES "workflow_states"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "workflow_transitions" ADD CONSTRAINT "workflow_transitions_toStateId_fkey" FOREIGN KEY ("toStateId") REFERENCES "workflow_states"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "workflow_transitions" ADD CONSTRAINT "workflow_transitions_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflows"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "status_change_history" ADD CONSTRAINT "status_change_history_changedBy_fkey" FOREIGN KEY ("changedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "status_change_history" ADD CONSTRAINT "status_change_history_fromStateId_fkey" FOREIGN KEY ("fromStateId") REFERENCES "workflow_states"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "status_change_history" ADD CONSTRAINT "status_change_history_toStateId_fkey" FOREIGN KEY ("toStateId") REFERENCES "workflow_states"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "status_change_history" ADD CONSTRAINT "status_change_history_transitionId_fkey" FOREIGN KEY ("transitionId") REFERENCES "workflow_transitions"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "status_change_history" ADD CONSTRAINT "status_change_history_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflows"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "notifications" ADD CONSTRAINT "notifications_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "scheduled_deletions" ADD CONSTRAINT "scheduled_deletions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "consent_records" ADD CONSTRAINT "consent_records_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "email_logs" ADD CONSTRAINT "email_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; +-- Foreign keys for the scrum lifecycle enhancement tables +ALTER TABLE "increment_integration_tests" ADD CONSTRAINT "increment_integration_tests_currentIncrementId_fkey" FOREIGN KEY ("currentIncrementId") REFERENCES "increments"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "increment_integration_tests" ADD CONSTRAINT "increment_integration_tests_priorIncrementId_fkey" FOREIGN KEY ("priorIncrementId") REFERENCES "increments"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "increment_integration_tests" ADD CONSTRAINT "increment_integration_tests_testedById_fkey" FOREIGN KEY ("testedById") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "product_goal_snapshots" ADD CONSTRAINT "product_goal_snapshots_goalId_fkey" FOREIGN KEY ("goalId") REFERENCES "product_goals"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "product_goal_snapshots" ADD CONSTRAINT "product_goal_snapshots_sprintReviewId_fkey" FOREIGN KEY ("sprintReviewId") REFERENCES "sprint_reviews"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "team_health_checks" ADD CONSTRAINT "team_health_checks_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "team_health_checks" ADD CONSTRAINT "team_health_checks_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "team_health_check_responses" ADD CONSTRAINT "team_health_check_responses_healthCheckId_fkey" FOREIGN KEY ("healthCheckId") REFERENCES "team_health_checks"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "team_health_check_responses" ADD CONSTRAINT "team_health_check_responses_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- Timebox foreign keys +ALTER TABLE "timeboxes" ADD CONSTRAINT "timeboxes_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "timeboxes" ADD CONSTRAINT "timeboxes_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE CASCADE ON UPDATE CASCADE; + -- ============================================ -- CHECK CONSTRAINTS FOR BUSINESS RULES -- ============================================ @@ -1425,17 +1107,12 @@ ALTER TABLE "product_backlog_items" ADD CONSTRAINT "chk_pbi_values" CHECK ("stor ALTER TABLE "increments" ADD CONSTRAINT "chk_increments_points" CHECK ("totalStoryPoints" >= 0); ALTER TABLE "burndown_data" ADD CONSTRAINT "chk_burndown_remaining" CHECK ("idealRemaining" >= 0); --- DROP GIN INDEXES -DROP INDEX IF EXISTS "product_backlog_items_labels_idx"; -DROP INDEX IF EXISTS "status_change_history_metadata_idx"; -DROP INDEX IF EXISTS "notifications_data_idx"; - -- ============================================ -- GIN INDEXES FOR JSONB AND ARRAY COLUMNS -- ============================================ CREATE INDEX "product_backlog_items_labels_idx" ON "product_backlog_items" USING GIN ("labels"); -CREATE INDEX "status_change_history_metadata_idx" ON "status_change_history" USING GIN ("metadata"); -CREATE INDEX "notifications_data_idx" ON "notifications" USING GIN ("data"); +CREATE INDEX "status_change_history_metadata_idx" ON "status_change_history" USING GIN ("metadata" jsonb_path_ops); +CREATE INDEX "notifications_data_idx" ON "notifications" USING GIN ("data" jsonb_path_ops); -- ============================================ -- PARTIAL INDEXES FOR COMMON QUERIES diff --git a/packages/backend/prisma/migrations/20260711055307_add_i18n_support/migration.sql b/packages/backend/prisma/migrations/20260711055307_add_i18n_support/migration.sql deleted file mode 100644 index 30dcefd4..00000000 --- a/packages/backend/prisma/migrations/20260711055307_add_i18n_support/migration.sql +++ /dev/null @@ -1,6 +0,0 @@ --- AlterTable: Add locale column to users table -ALTER TABLE "users" ADD COLUMN "locale" VARCHAR(10) NOT NULL DEFAULT 'en'; - --- AlterTable -ALTER TABLE "notifications" ADD COLUMN "messageKey" TEXT, -ADD COLUMN "params" JSONB; diff --git a/packages/backend/prisma/migrations/20260811000000_enhance_scrum_lifecycle/migration.sql b/packages/backend/prisma/migrations/20260811000000_enhance_scrum_lifecycle/migration.sql deleted file mode 100644 index 51771ca8..00000000 --- a/packages/backend/prisma/migrations/20260811000000_enhance_scrum_lifecycle/migration.sql +++ /dev/null @@ -1,146 +0,0 @@ --- AlterTable: Add smNotes to sprints table -ALTER TABLE "sprints" ADD COLUMN "smNotes" TEXT; - --- AlterTable: Add integrationVerified to increments table -ALTER TABLE "increments" ADD COLUMN "integrationVerified" BOOLEAN NOT NULL DEFAULT false; - --- AlterTable: Add smNotes to sprint_reviews table -ALTER TABLE "sprint_reviews" ADD COLUMN "smNotes" TEXT; - --- AlterTable: Add smNotes to sprint_retrospectives table -ALTER TABLE "sprint_retrospectives" ADD COLUMN "smNotes" TEXT; - --- AlterTable: Add productGoalAssessment to stakeholder_feedback table -ALTER TABLE "stakeholder_feedback" ADD COLUMN "productGoalAssessment" TEXT; - --- CreateEnum: IntegrationTestResult -CREATE TYPE "IntegrationTestResult" AS ENUM ('PENDING', 'PASSED', 'FAILED'); - --- CreateEnum: ScrumValue -CREATE TYPE "ScrumValue" AS ENUM ('COMMITMENT', 'FOCUS', 'OPENNESS', 'RESPECT', 'COURAGE'); - --- CreateEnum: HealthCheckStatus -CREATE TYPE "HealthCheckStatus" AS ENUM ('OPEN', 'CLOSED'); - --- CreateTable: IncrementIntegrationTest -CREATE TABLE "increment_integration_tests" ( - "id" UUID NOT NULL, - "currentIncrementId" UUID NOT NULL, - "priorIncrementId" UUID NOT NULL, - "testResult" "IntegrationTestResult" NOT NULL DEFAULT 'PENDING', - "testedById" UUID NOT NULL, - "testedAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "notes" TEXT, - "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "createdBy" TEXT, - "updatedAt" TIMESTAMPTZ(3) NOT NULL, - "updatedBy" TEXT, - - CONSTRAINT "increment_integration_tests_pkey" PRIMARY KEY ("id") -); - --- CreateTable: ProductGoalSnapshot -CREATE TABLE "product_goal_snapshots" ( - "id" UUID NOT NULL, - "goalId" UUID NOT NULL, - "sprintReviewId" UUID NOT NULL, - "successMetricValues" JSONB, - "completedPbiCount" INTEGER NOT NULL DEFAULT 0, - "completedStoryPoints" INTEGER NOT NULL DEFAULT 0, - "assessment" TEXT, - "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "createdBy" TEXT, - "updatedAt" TIMESTAMPTZ(3) NOT NULL, - "updatedBy" TEXT, - - CONSTRAINT "product_goal_snapshots_pkey" PRIMARY KEY ("id") -); - --- CreateTable: TeamHealthCheck -CREATE TABLE "team_health_checks" ( - "id" UUID NOT NULL, - "teamId" UUID NOT NULL, - "sprintId" UUID, - "status" "HealthCheckStatus" NOT NULL DEFAULT 'OPEN', - "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "createdBy" TEXT, - "updatedAt" TIMESTAMPTZ(3) NOT NULL, - "updatedBy" TEXT, - - CONSTRAINT "team_health_checks_pkey" PRIMARY KEY ("id") -); - --- CreateTable: TeamHealthCheckResponse -CREATE TABLE "team_health_check_responses" ( - "id" UUID NOT NULL, - "healthCheckId" UUID NOT NULL, - "userId" UUID NOT NULL, - "scrumValue" "ScrumValue" NOT NULL, - "score" INTEGER NOT NULL, - "anonymous" BOOLEAN NOT NULL DEFAULT false, - "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "createdBy" TEXT, - "updatedAt" TIMESTAMPTZ(3) NOT NULL, - "updatedBy" TEXT, - - CONSTRAINT "team_health_check_responses_pkey" PRIMARY KEY ("id") -); - --- CreateIndex: increments team/status/createdAt for SM dashboard queries -CREATE INDEX "increments_teamId_status_createdAt_idx" ON "increments"("teamId", "status", "createdAt"); - --- CreateIndex: impediments team/status/createdAt for SM dashboard queries -CREATE INDEX "impediments_teamId_status_createdAt_idx" ON "impediments"("teamId", "status", "createdAt"); - --- CreateIndex: retro_action_items owner/status/dueDate for SM dashboard queries -CREATE INDEX "retro_action_items_ownerId_status_dueDate_idx" ON "retro_action_items"("ownerId", "status", "dueDate"); - --- CreateIndex: increment_integration_tests -CREATE UNIQUE INDEX "increment_integration_tests_currentIncrementId_priorIncrementId_key" ON "increment_integration_tests"("currentIncrementId", "priorIncrementId"); -CREATE INDEX "increment_integration_tests_currentIncrementId_idx" ON "increment_integration_tests"("currentIncrementId"); -CREATE INDEX "increment_integration_tests_priorIncrementId_idx" ON "increment_integration_tests"("priorIncrementId"); -CREATE INDEX "increment_integration_tests_testedById_idx" ON "increment_integration_tests"("testedById"); - --- CreateIndex: product_goal_snapshots -CREATE UNIQUE INDEX "product_goal_snapshots_goalId_sprintReviewId_key" ON "product_goal_snapshots"("goalId", "sprintReviewId"); -CREATE INDEX "product_goal_snapshots_goalId_idx" ON "product_goal_snapshots"("goalId"); -CREATE INDEX "product_goal_snapshots_sprintReviewId_idx" ON "product_goal_snapshots"("sprintReviewId"); - --- CreateIndex: team_health_checks -CREATE INDEX "team_health_checks_teamId_idx" ON "team_health_checks"("teamId"); -CREATE INDEX "team_health_checks_sprintId_idx" ON "team_health_checks"("sprintId"); -CREATE INDEX "team_health_checks_status_idx" ON "team_health_checks"("status"); -CREATE INDEX "team_health_checks_teamId_status_createdAt_idx" ON "team_health_checks"("teamId", "status", "createdAt"); - --- CreateIndex: team_health_check_responses -CREATE UNIQUE INDEX "team_health_check_responses_healthCheckId_userId_scrumValue_key" ON "team_health_check_responses"("healthCheckId", "userId", "scrumValue"); -CREATE INDEX "team_health_check_responses_healthCheckId_idx" ON "team_health_check_responses"("healthCheckId"); -CREATE INDEX "team_health_check_responses_userId_idx" ON "team_health_check_responses"("userId"); -CREATE INDEX "team_health_check_responses_healthCheckId_scrumValue_idx" ON "team_health_check_responses"("healthCheckId", "scrumValue"); - --- AddForeignKey: increment_integration_tests currentIncrementId -> increments -ALTER TABLE "increment_integration_tests" ADD CONSTRAINT "increment_integration_tests_currentIncrementId_fkey" FOREIGN KEY ("currentIncrementId") REFERENCES "increments"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey: increment_integration_tests priorIncrementId -> increments -ALTER TABLE "increment_integration_tests" ADD CONSTRAINT "increment_integration_tests_priorIncrementId_fkey" FOREIGN KEY ("priorIncrementId") REFERENCES "increments"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey: increment_integration_tests testedById -> users -ALTER TABLE "increment_integration_tests" ADD CONSTRAINT "increment_integration_tests_testedById_fkey" FOREIGN KEY ("testedById") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey: product_goal_snapshots goalId -> product_goals -ALTER TABLE "product_goal_snapshots" ADD CONSTRAINT "product_goal_snapshots_goalId_fkey" FOREIGN KEY ("goalId") REFERENCES "product_goals"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey: product_goal_snapshots sprintReviewId -> sprint_reviews -ALTER TABLE "product_goal_snapshots" ADD CONSTRAINT "product_goal_snapshots_sprintReviewId_fkey" FOREIGN KEY ("sprintReviewId") REFERENCES "sprint_reviews"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey: team_health_checks teamId -> teams -ALTER TABLE "team_health_checks" ADD CONSTRAINT "team_health_checks_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey: team_health_checks sprintId -> sprints -ALTER TABLE "team_health_checks" ADD CONSTRAINT "team_health_checks_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "sprints"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey: team_health_check_responses healthCheckId -> team_health_checks -ALTER TABLE "team_health_check_responses" ADD CONSTRAINT "team_health_check_responses_healthCheckId_fkey" FOREIGN KEY ("healthCheckId") REFERENCES "team_health_checks"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey: team_health_check_responses userId -> users -ALTER TABLE "team_health_check_responses" ADD CONSTRAINT "team_health_check_responses_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/backend/prisma/schema.prisma b/packages/backend/prisma/schema.prisma index 986a00f7..12e928b2 100644 --- a/packages/backend/prisma/schema.prisma +++ b/packages/backend/prisma/schema.prisma @@ -8,49 +8,49 @@ datasource db { } model User { - id String @id @db.Uuid - email String @unique - password String - firstName String - lastName String - avatarUrl String? - locale String @default("en") @db.VarChar(10) - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - marketingOptIn Boolean @default(false) @map("marketing_opt_in") - marketingOptInAt DateTime? @map("marketing_opt_in_at") @db.Timestamptz(3) - termsAcceptedAt DateTime? @map("terms_accepted_at") @db.Timestamptz(3) - backlogAdjustments BacklogAdjustment[] - consentRecords ConsentRecord[] - dailyUpdates DailyUpdate[] - dodVerifications DoDChecklistVerification[] - dorVerifications DoRChecklistVerification[] - emailLogs EmailLog[] - ownedImpediments Impediment[] @relation("ImpedimentOwner") - reportedImpediments Impediment[] @relation("ImpedimentReporter") - increments Increment[] @relation("IncrementCreator") - notifications Notification[] - createdPBIs ProductBacklogItem[] @relation("PBICreator") - createdGoals ProductGoal[] @relation("GoalCreator") - passwordResetTokens PasswordResetToken[] - refreshTokens RefreshToken[] - actionItems RetroActionItem[] @relation("ActionItemOwner") - integrationTests IncrementIntegrationTest[] - healthCheckResponses TeamHealthCheckResponse[] - retroItemVotes RetroItemVote[] @relation("RetroItemVoter") - retrospectiveItems RetrospectiveItem[] @relation("RetroItemAuthor") - scheduledDeletions ScheduledDeletion[] - sprintBacklogChanges SprintBacklogChange[] @relation("SprintBacklogChangeCreator") - retrospectives SprintRetrospective[] @relation("RetroFacilitator") - sprintReviews SprintReview[] @relation("ReviewCreator") - stakeholderFeedbacks StakeholderFeedback[] - statusChanges StatusChangeHistory[] @relation("StatusChanger") - assignedTasks Task[] @relation("TaskAssignee") - teamMemberships TeamMember[] - createdTeams Team[] @relation("TeamCreator") - createdWorkflows Workflow[] @relation("WorkflowCreator") + id String @id @db.Uuid + email String @unique + password String + firstName String + lastName String + avatarUrl String? + locale String @default("en") @db.VarChar(10) + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + marketingOptIn Boolean @default(false) @map("marketing_opt_in") + marketingOptInAt DateTime? @map("marketing_opt_in_at") @db.Timestamptz(3) + termsAcceptedAt DateTime? @map("terms_accepted_at") @db.Timestamptz(3) + backlogAdjustments BacklogAdjustment[] + consentRecords ConsentRecord[] + dailyScrumParticipants DailyScrumParticipant[] + dodVerifications DoDChecklistVerification[] + dorVerifications DoRChecklistVerification[] + emailLogs EmailLog[] + ownedImpediments Impediment[] @relation("ImpedimentOwner") + reportedImpediments Impediment[] @relation("ImpedimentReporter") + increments Increment[] @relation("IncrementCreator") + notifications Notification[] + createdPBIs ProductBacklogItem[] @relation("PBICreator") + createdGoals ProductGoal[] @relation("GoalCreator") + passwordResetTokens PasswordResetToken[] + refreshTokens RefreshToken[] + actionItems RetroActionItem[] @relation("ActionItemOwner") + integrationTests IncrementIntegrationTest[] + healthCheckResponses TeamHealthCheckResponse[] + retroItemVotes RetroItemVote[] @relation("RetroItemVoter") + retrospectiveItems RetrospectiveItem[] @relation("RetroItemAuthor") + scheduledDeletions ScheduledDeletion[] + sprintBacklogChanges SprintBacklogChange[] @relation("SprintBacklogChangeCreator") + retrospectives SprintRetrospective[] @relation("RetroFacilitator") + sprintReviews SprintReview[] @relation("ReviewCreator") + stakeholderFeedbacks StakeholderFeedback[] + statusChanges StatusChangeHistory[] @relation("StatusChanger") + assignedTasks Task[] @relation("TaskAssignee") + teamMemberships TeamMember[] + createdTeams Team[] @relation("TeamCreator") + createdWorkflows Workflow[] @relation("WorkflowCreator") @@map("users") } @@ -76,6 +76,7 @@ model Team { sprintReviews SprintReview[] sprints Sprint[] members TeamMember[] + timeboxes Timebox[] creator User? @relation("TeamCreator", fields: [createdBy], references: [id], onDelete: SetNull) @@index([createdBy]) @@ -101,22 +102,22 @@ model TeamMember { } model ProductGoal { - id String @id @db.Uuid - teamId String @db.Uuid + id String @id @db.Uuid + teamId String @db.Uuid title String description String? status ProductGoalStatus - targetDate DateTime? @db.Timestamptz(3) + targetDate DateTime? @db.Timestamptz(3) successMetrics String? strategicAlignment String? - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid backlogItems ProductBacklogItem[] snapshots ProductGoalSnapshot[] - creator User? @relation("GoalCreator", fields: [createdBy], references: [id], onDelete: SetNull) - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + creator User? @relation("GoalCreator", fields: [createdBy], references: [id], onDelete: SetNull) + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) sprints Sprint[] @@index([teamId]) @@ -158,6 +159,7 @@ model ProductBacklogItem { @@index([priority]) @@index([teamId, status]) @@index([teamId, status, priority]) + @@index([labels], type: Gin) @@map("product_backlog_items") } @@ -177,7 +179,7 @@ model Sprint { updatedAt DateTime @updatedAt @db.Timestamptz(3) updatedBy String? @db.Uuid burndownData BurndownData[] - dailyUpdates DailyUpdate[] + dailyScrums DailyScrum[] generatedSprint GeneratedSprint? impediments Impediment[] increments Increment[] @@ -186,6 +188,7 @@ model Sprint { retrospective SprintRetrospective? sprintReview SprintReview? healthChecks TeamHealthCheck[] + timeboxes Timebox[] goal ProductGoal? @relation(fields: [goalId], references: [id], onDelete: SetNull) team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) tasks Task[] @@ -202,16 +205,17 @@ model Sprint { } model SprintBacklogItem { - id String @id @db.Uuid - sprintId String @db.Uuid - pbiId String @db.Uuid - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - changes SprintBacklogChange[] - pbi ProductBacklogItem @relation(fields: [pbiId], references: [id], onDelete: Cascade) - sprint Sprint @relation(fields: [sprintId], references: [id], onDelete: Cascade) + id String @id @db.Uuid + sprintId String @db.Uuid + pbiId String @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + changes SprintBacklogChange[] + dailyScrumAdjustments DailyScrumBacklogItem[] + pbi ProductBacklogItem @relation(fields: [pbiId], references: [id], onDelete: Cascade) + sprint Sprint @relation(fields: [sprintId], references: [id], onDelete: Cascade) @@unique([sprintId, pbiId]) @@map("sprint_backlog_items") @@ -268,26 +272,63 @@ model Task { @@map("tasks") } -model DailyUpdate { - id String @id @db.Uuid - sprintId String @db.Uuid - userId String @db.Uuid - updateDate DateTime @db.Date - yesterdayWork String? - todayWork String? - impediment String? - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - sprint Sprint @relation(fields: [sprintId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - impedimentRecord Impediment? - - @@unique([sprintId, userId, updateDate]) - @@index([sprintId, updateDate]) - @@index([updateDate]) - @@map("daily_updates") +model DailyScrum { + id String @id @db.Uuid + sprintId String @db.Uuid + scrumDate DateTime @db.Date + progressNotes String? + adaptationsNotes String? + planForNextDay String? + // The structural focus the Developers chose (Scrum Guide: "Developers choose + // structure"). Stored on the shared team record so all team members can see + // how the event is being run. Stored as a plain string to keep it forward + // compatible; validated against DAILY_SCRUM_FOCUS_MODES at the API boundary. + focusMode String? + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + sprint Sprint @relation(fields: [sprintId], references: [id], onDelete: Cascade) + backlogAdjustments DailyScrumBacklogItem[] + participants DailyScrumParticipant[] + + @@unique([sprintId, scrumDate]) + @@index([sprintId, scrumDate]) + @@index([scrumDate]) + @@map("daily_scrums") +} + +model DailyScrumBacklogItem { + id String @id @db.Uuid + dailyScrumId String @db.Uuid + sprintBacklogItemId String @db.Uuid + action String + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + dailyScrum DailyScrum @relation(fields: [dailyScrumId], references: [id], onDelete: Cascade) + sprintBacklogItem SprintBacklogItem @relation(fields: [sprintBacklogItemId], references: [id], onDelete: Cascade) + + @@unique([dailyScrumId, sprintBacklogItemId]) + @@index([dailyScrumId]) + @@index([sprintBacklogItemId]) + @@map("daily_scrum_backlog_items") +} + +model DailyScrumParticipant { + id String @id @db.Uuid + dailyScrumId String @db.Uuid + userId String @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + dailyScrum DailyScrum @relation(fields: [dailyScrumId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([dailyScrumId, userId]) + @@index([dailyScrumId]) + @@index([userId]) + @@map("daily_scrum_participants") } model BurndownData { @@ -317,12 +358,10 @@ model Impediment { status ImpedimentStatus @default(OPEN) resolution String? resolvedAt DateTime? @db.Timestamptz(3) - dailyUpdateId String? @unique @db.Uuid createdAt DateTime @default(now()) @db.Timestamptz(3) createdBy String? @db.Uuid updatedAt DateTime @updatedAt @db.Timestamptz(3) updatedBy String? @db.Uuid - dailyUpdate DailyUpdate? @relation(fields: [dailyUpdateId], references: [id], onDelete: SetNull) owner User? @relation("ImpedimentOwner", fields: [ownerId], references: [id], onDelete: SetNull) reportedBy User @relation("ImpedimentReporter", fields: [reportedById], references: [id], onDelete: Restrict) sprint Sprint? @relation(fields: [sprintId], references: [id], onDelete: SetNull) @@ -449,26 +488,26 @@ model DoRChecklistVerification { } model Increment { - id String @id @db.Uuid - sprintId String @db.Uuid - teamId String @db.Uuid - name String - description String? - totalStoryPoints Int @default(0) - status IncrementStatus @default(DRAFT) - integrationVerified Boolean @default(false) - deliveredAt DateTime? @db.Timestamptz(3) - deliveryMethod DeliveryMethod? - notes String? - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - pbis IncrementPBI[] - creator User? @relation("IncrementCreator", fields: [createdBy], references: [id], onDelete: SetNull) - sprint Sprint @relation(fields: [sprintId], references: [id], onDelete: Cascade) - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) - sprintReview SprintReview? + id String @id @db.Uuid + sprintId String @db.Uuid + teamId String @db.Uuid + name String + description String? + totalStoryPoints Int @default(0) + status IncrementStatus @default(DRAFT) + integrationVerified Boolean @default(false) + deliveredAt DateTime? @db.Timestamptz(3) + deliveryMethod DeliveryMethod? + notes String? + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + pbis IncrementPBI[] + creator User? @relation("IncrementCreator", fields: [createdBy], references: [id], onDelete: SetNull) + sprint Sprint @relation(fields: [sprintId], references: [id], onDelete: Cascade) + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + sprintReview SprintReview? integrationTestsAsCurrent IncrementIntegrationTest[] @relation("IntegrationCurrentIncrement") integrationTestsAsPrior IncrementIntegrationTest[] @relation("IntegrationPriorIncrement") @@ -482,60 +521,60 @@ model Increment { } model IncrementIntegrationTest { - id String @id @db.Uuid - currentIncrementId String @db.Uuid - priorIncrementId String @db.Uuid - testResult IntegrationTestResult @default(PENDING) - testedById String @db.Uuid - testedAt DateTime @default(now()) @db.Timestamptz(3) - notes String? - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - currentIncrement Increment @relation("IntegrationCurrentIncrement", fields: [currentIncrementId], references: [id], onDelete: Cascade) - priorIncrement Increment @relation("IntegrationPriorIncrement", fields: [priorIncrementId], references: [id], onDelete: Cascade) - testedBy User @relation(fields: [testedById], references: [id], onDelete: Restrict) + id String @id @db.Uuid + currentIncrementId String @db.Uuid + priorIncrementId String @db.Uuid + testResult IntegrationTestResult @default(PENDING) + testedById String @db.Uuid + testedAt DateTime @default(now()) @db.Timestamptz(3) + notes String? + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + currentIncrement Increment @relation("IntegrationCurrentIncrement", fields: [currentIncrementId], references: [id], onDelete: Cascade) + priorIncrement Increment @relation("IntegrationPriorIncrement", fields: [priorIncrementId], references: [id], onDelete: Cascade) + testedBy User @relation(fields: [testedById], references: [id], onDelete: Restrict) + @@unique([currentIncrementId, priorIncrementId]) @@index([currentIncrementId]) @@index([priorIncrementId]) @@index([testedById]) - @@unique([currentIncrementId, priorIncrementId]) @@map("increment_integration_tests") } model ProductGoalSnapshot { - id String @id @db.Uuid - goalId String @db.Uuid - sprintReviewId String @db.Uuid - successMetricValues Json? - completedPbiCount Int @default(0) - completedStoryPoints Int @default(0) - assessment String? - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - goal ProductGoal @relation(fields: [goalId], references: [id], onDelete: Cascade) - sprintReview SprintReview @relation(fields: [sprintReviewId], references: [id], onDelete: Cascade) + id String @id @db.Uuid + goalId String @db.Uuid + sprintReviewId String @db.Uuid + successMetricValues Json? + completedPbiCount Int @default(0) + completedStoryPoints Int @default(0) + assessment String? + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + goal ProductGoal @relation(fields: [goalId], references: [id], onDelete: Cascade) + sprintReview SprintReview @relation(fields: [sprintReviewId], references: [id], onDelete: Cascade) + @@unique([goalId, sprintReviewId]) @@index([goalId]) @@index([sprintReviewId]) - @@unique([goalId, sprintReviewId]) @@map("product_goal_snapshots") } model TeamHealthCheck { - id String @id @db.Uuid - teamId String @db.Uuid - sprintId String? @db.Uuid - status HealthCheckStatus @default(OPEN) - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) - sprint Sprint? @relation(fields: [sprintId], references: [id], onDelete: SetNull) + id String @id @db.Uuid + teamId String @db.Uuid + sprintId String? @db.Uuid + status HealthCheckStatus @default(OPEN) + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + sprint Sprint? @relation(fields: [sprintId], references: [id], onDelete: SetNull) responses TeamHealthCheckResponse[] @@index([teamId]) @@ -546,18 +585,18 @@ model TeamHealthCheck { } model TeamHealthCheckResponse { - id String @id @db.Uuid - healthCheckId String @db.Uuid - userId String @db.Uuid + id String @id @db.Uuid + healthCheckId String @db.Uuid + userId String @db.Uuid scrumValue ScrumValue score Int - anonymous Boolean @default(false) - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid + anonymous Boolean @default(false) + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid healthCheck TeamHealthCheck @relation(fields: [healthCheckId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([healthCheckId, userId, scrumValue]) @@index([healthCheckId]) @@ -585,26 +624,26 @@ model IncrementPBI { } model SprintReview { - id String @id @db.Uuid - sprintId String @unique @db.Uuid - teamId String @db.Uuid - incrementId String @unique @db.Uuid - reviewDate DateTime @db.Timestamptz(3) - summary String? - status String @default("in_progress") - smNotes String? - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - backlogAdjustments BacklogAdjustment[] - attendees ReviewAttendee[] + id String @id @db.Uuid + sprintId String @unique @db.Uuid + teamId String @db.Uuid + incrementId String @unique @db.Uuid + reviewDate DateTime @db.Timestamptz(3) + summary String? + status String @default("in_progress") + smNotes String? + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + backlogAdjustments BacklogAdjustment[] + attendees ReviewAttendee[] productGoalSnapshots ProductGoalSnapshot[] - creator User? @relation("ReviewCreator", fields: [createdBy], references: [id], onDelete: SetNull) - increment Increment @relation(fields: [incrementId], references: [id], onDelete: Cascade) - sprint Sprint @relation(fields: [sprintId], references: [id], onDelete: Cascade) - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) - feedback StakeholderFeedback[] + creator User? @relation("ReviewCreator", fields: [createdBy], references: [id], onDelete: SetNull) + increment Increment @relation(fields: [incrementId], references: [id], onDelete: Cascade) + sprint Sprint @relation(fields: [sprintId], references: [id], onDelete: Cascade) + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + feedback StakeholderFeedback[] @@index([teamId]) @@index([reviewDate]) @@ -636,15 +675,15 @@ model StakeholderFeedback { productGoalAssessment String? category FeedbackCategory @default(POSITIVE) relatedPbiId String? @db.Uuid - actionRequired Boolean @default(false) - actionTaken Boolean @default(false) - ownerId String? @db.Uuid - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - owner User? @relation(fields: [ownerId], references: [id], onDelete: SetNull) - review SprintReview @relation(fields: [reviewId], references: [id], onDelete: Cascade) + actionRequired Boolean @default(false) + actionTaken Boolean @default(false) + ownerId String? @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + owner User? @relation(fields: [ownerId], references: [id], onDelete: SetNull) + review SprintReview @relation(fields: [reviewId], references: [id], onDelete: Cascade) @@index([reviewId]) @@index([ownerId]) @@ -918,6 +957,7 @@ model StatusChangeHistory { @@index([entityId]) @@index([createdAt]) @@index([entityType, entityId]) + @@index([metadata(ops: JsonbPathOps)], type: Gin) @@map("status_change_history") } @@ -925,11 +965,11 @@ model Notification { id String @id @db.Uuid userId String @db.Uuid type NotificationType - title String // For backward compatibility and email/push fallback - message String? // For backward compatibility and email/push fallback - messageKey String? // Translation key for display-time localization - params Json? // Translation parameters - data Json? // Additional metadata (teamId, etc) + title String // For backward compatibility and email/push fallback + message String? // For backward compatibility and email/push fallback + messageKey String? // Translation key for display-time localization + params Json? // Translation parameters + data Json? // Additional metadata (teamId, etc) isRead Boolean @default(false) readAt DateTime? @db.Timestamptz(3) createdAt DateTime @default(now()) @db.Timestamptz(3) @@ -941,6 +981,7 @@ model Notification { @@index([userId]) @@index([userId, isRead]) @@index([createdAt]) + @@index([data(ops: JsonbPathOps)], type: Gin) @@map("notifications") } @@ -1017,26 +1058,26 @@ model ConsentRecord { } model EmailLog { - id String @id @db.Uuid - userId String? @db.Uuid - email String - type EmailType - subject String - status EmailStatus - provider String - messageId String? + id String @id @db.Uuid + userId String? @db.Uuid + email String + type EmailType + subject String + status EmailStatus + provider String + messageId String? errorMessage String? - metadata Json @default("{}") - testMode Boolean @default(false) - sentAt DateTime? @db.Timestamptz(3) - deliveredAt DateTime? @db.Timestamptz(3) - openedAt DateTime? @db.Timestamptz(3) - clickedAt DateTime? @db.Timestamptz(3) - createdAt DateTime @default(now()) @db.Timestamptz(3) - createdBy String? @db.Uuid - updatedAt DateTime @updatedAt @db.Timestamptz(3) - updatedBy String? @db.Uuid - user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + metadata Json @default("{}") + testMode Boolean @default(false) + sentAt DateTime? @db.Timestamptz(3) + deliveredAt DateTime? @db.Timestamptz(3) + openedAt DateTime? @db.Timestamptz(3) + clickedAt DateTime? @db.Timestamptz(3) + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([email]) @@ -1062,10 +1103,37 @@ model PasswordResetToken { @@map("password_reset_tokens") } +model Timebox { + id String @id @db.Uuid + teamId String @db.Uuid + eventType String + sprintId String? @db.Uuid + date DateTime @db.Date + status TimeboxStatus @default(IDLE) + startedAt DateTime? @db.Timestamptz(3) + pausedAt DateTime? @db.Timestamptz(3) + accumulatedMs Int @default(0) + version Int @default(0) + concludedAt DateTime? @db.Timestamptz(3) + concludedElapsedMs Int? + createdAt DateTime @default(now()) @db.Timestamptz(3) + createdBy String? @db.Uuid + updatedAt DateTime @updatedAt @db.Timestamptz(3) + updatedBy String? @db.Uuid + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + sprint Sprint? @relation(fields: [sprintId], references: [id], onDelete: Cascade) + + @@unique([teamId, eventType, sprintId, date]) + @@index([teamId]) + @@index([sprintId]) + @@index([date]) + @@map("timeboxes") +} + enum UserRole { PRODUCT_OWNER SCRUM_MASTER - DEVELOPER + DEVELOPERS } enum ItemStatus { @@ -1079,10 +1147,12 @@ enum ItemStatus { enum TaskStatus { TODO IN_PROGRESS + REVIEW DONE } enum SprintStatus { + DRAFT PLANNED ACTIVE COMPLETED @@ -1167,6 +1237,12 @@ enum RetrospectiveStatus { COMPLETED } +enum TimeboxStatus { + IDLE + RUNNING + PAUSED +} + enum SprintDuration { ONE_WEEK TWO_WEEKS @@ -1186,7 +1262,7 @@ enum NotificationType { TEAM_REMOVAL TASK_ASSIGNMENT IMPEDIMENT_ASSIGNMENT - DAILY_UPDATE_REMINDER + DAILY_SCRUM_SIGNAL TEAM_CREATED TEAM_UPDATED TEAM_DELETED diff --git a/packages/backend/scripts/generate-load-test-data.ts b/packages/backend/scripts/generate-load-test-data.ts index e4f98cba..395f0bdf 100644 --- a/packages/backend/scripts/generate-load-test-data.ts +++ b/packages/backend/scripts/generate-load-test-data.ts @@ -111,7 +111,7 @@ async function generateTestData(options: DataGenerationOptions) { }, }); - const role = u === 0 ? 'PRODUCT_OWNER' : u === 1 ? 'SCRUM_MASTER' : 'DEVELOPER'; + const role = u === 0 ? 'PRODUCT_OWNER' : u === 1 ? 'SCRUM_MASTER' : 'DEVELOPERS'; const teamMemberId = generateUUIDv7(); await prisma.teamMember.create({ diff --git a/packages/backend/src/__tests__/e2e/daily-updates.e2e.test.ts b/packages/backend/src/__tests__/e2e/daily-updates.e2e.test.ts deleted file mode 100644 index 47c62407..00000000 --- a/packages/backend/src/__tests__/e2e/daily-updates.e2e.test.ts +++ /dev/null @@ -1,895 +0,0 @@ -import { describe, it, expect, afterEach } from 'vitest'; -import request from 'supertest'; -import app from '../../app'; -import prisma from '../../utils/prisma'; -import { - uniqueTestId, - HTTP_STATUS, - ERROR_CODES, - DEFAULT_PASSWORD, - createTestUser, - createTestTeamInDb, - addTeamMember, - createTestSprintInDb, - createTestDailyUpdateInDb, - cleanupUsers, - cleanupTeams, - ROLES, - SPRINT_STATUSES, - getCsrfToken, - extractCsrfFromCookies, - CSRF_CONSTANTS, -} from '@e2e-helpers'; - -describe('E2E: Daily Updates', () => { - const testEmails: string[] = []; - const testTeamNames: string[] = []; - - afterEach(async () => { - await cleanupTeams(testTeamNames); - await cleanupUsers(testEmails); - testEmails.length = 0; - testTeamNames.length = 0; - }); - - const loginAndGetCookies = async (email: string): Promise