fix(project) : permet de choisir un workflow à la création + filet par défaut
Pull Request — Quality gate / Frontend (build) (pull_request) Successful in 38s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m39s

La création de projet échouait : `Project.workflow` est obligatoire mais
n'était jamais fourni (formulaire frontend, MCP create-project), tout POST
/api/projects partait en erreur de validation/contrainte NOT NULL.

- ProjectDefaultWorkflowListener (prePersist) : assigne le workflow par
  défaut quand aucun n'est fourni, couvrant API Platform, API brute et MCP.
- retrait de l'Assert\NotNull sur Project::workflow (la validation tournait
  avant le flush et empêchait le filet) ; la contrainte DB reste le garde-fou.
- CreateProjectTool (MCP) : paramètre optionnel workflowId.
- ProjectDrawer : sélecteur Workflow en création, pré-rempli sur le défaut,
  IRI envoyée dans le payload.
- tests fonctionnels : création avec et sans workflow.
This commit is contained in:
Matthieu
2026-06-26 16:42:02 +02:00
parent 386242c84d
commit d3abb584a9
6 changed files with 177 additions and 2 deletions
@@ -32,6 +32,13 @@
empty-option-label="Aucun client"
group-class="w-full"
/>
<MalioSelect
v-if="!isEditing"
v-model="form.workflowId"
:options="workflowOptions"
label="Workflow"
group-class="w-full"
/>
<div class="mt-4">
<ColorPicker v-model="form.color" />
</div>
@@ -124,10 +131,12 @@
<script setup lang="ts">
import type { Project, ProjectWrite } from '~/modules/project-management/services/dto/project'
import type { Workflow } from '~/modules/project-management/services/dto/workflow'
import type { Client } from '~/modules/directory/services/dto/client'
import type { GiteaRepository } from '~/modules/integration/services/dto/gitea'
import type { BookStackShelf } from '~/modules/integration/services/dto/bookstack'
import { useProjectService } from '~/modules/project-management/services/projects'
import { useWorkflowService } from '~/modules/project-management/services/workflows'
import { useGiteaService } from '~/modules/integration/services/gitea'
import { useBookStackService } from '~/modules/integration/services/bookstack'
@@ -174,12 +183,24 @@ const bookstackShelfOptions = computed(() =>
bookstackShelves.value.map(s => ({ label: s.name, value: s.id }))
)
const { getAll: getAllWorkflows } = useWorkflowService()
const workflows = ref<Workflow[]>([])
const workflowOptions = computed(() =>
workflows.value.map(w => ({ label: w.name, value: w.id }))
)
function defaultWorkflowId(): number | null {
return (workflows.value.find(w => w.isDefault) ?? workflows.value[0])?.id ?? null
}
const form = reactive({
code: '',
name: '',
description: '',
color: '#222783',
clientId: null as number | null,
workflowId: null as number | null,
giteaRepoFullName: null as string | null,
bookstackShelfId: null as number | null,
})
@@ -222,6 +243,7 @@ watch(() => props.modelValue, (open) => {
form.description = ''
form.color = '#222783'
form.clientId = null
form.workflowId = defaultWorkflowId()
form.giteaRepoFullName = null
form.bookstackShelfId = null
}
@@ -269,6 +291,9 @@ async function handleSubmit() {
await update(props.project.id, payload)
} else {
payload.code = form.code
if (form.workflowId) {
payload.workflow = `/api/workflows/${form.workflowId}`
}
await create(payload)
}
@@ -308,6 +333,15 @@ async function handleArchiveToggle() {
}
onMounted(async () => {
try {
workflows.value = await getAllWorkflows()
// Si le drawer est déjà ouvert en création, pré-remplir une fois les workflows chargés.
if (props.modelValue && !props.project && !form.workflowId) {
form.workflowId = defaultWorkflowId()
}
} catch {
// Workflows indisponibles, ignore (le serveur assignera le défaut)
}
try {
giteaRepos.value = await listRepositories()
} catch {